@jordancoin/notioncli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,663 @@
1
+ // test/unit.test.js — Pure function tests (no API calls)
2
+
3
+ const { describe, it } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const {
6
+ richTextToPlain,
7
+ propValue,
8
+ buildPropValue,
9
+ printTable,
10
+ pagesToRows,
11
+ formatCsv,
12
+ formatYaml,
13
+ buildFilterFromSchema,
14
+ UUID_REGEX,
15
+ } = require('../lib/helpers');
16
+
17
+ // ─── richTextToPlain ───────────────────────────────────────────────────────────
18
+
19
+ describe('richTextToPlain', () => {
20
+ it('returns empty string for null', () => {
21
+ assert.equal(richTextToPlain(null), '');
22
+ });
23
+
24
+ it('returns empty string for undefined', () => {
25
+ assert.equal(richTextToPlain(undefined), '');
26
+ });
27
+
28
+ it('returns empty string for empty array', () => {
29
+ assert.equal(richTextToPlain([]), '');
30
+ });
31
+
32
+ it('returns empty string for non-array truthy value', () => {
33
+ assert.equal(richTextToPlain('some string'), '');
34
+ assert.equal(richTextToPlain(42), '');
35
+ assert.equal(richTextToPlain({}), '');
36
+ });
37
+
38
+ it('extracts plain text from single item', () => {
39
+ const rt = [{ plain_text: 'Hello' }];
40
+ assert.equal(richTextToPlain(rt), 'Hello');
41
+ });
42
+
43
+ it('concatenates plain text from multiple items', () => {
44
+ const rt = [
45
+ { plain_text: 'Hello ' },
46
+ { plain_text: 'World' },
47
+ ];
48
+ assert.equal(richTextToPlain(rt), 'Hello World');
49
+ });
50
+
51
+ it('handles items without plain_text', () => {
52
+ const rt = [{ plain_text: 'Hi' }, {}, { plain_text: ' there' }];
53
+ assert.equal(richTextToPlain(rt), 'Hi there');
54
+ });
55
+ });
56
+
57
+ // ─── propValue ─────────────────────────────────────────────────────────────────
58
+
59
+ describe('propValue', () => {
60
+ it('returns empty string for null/undefined', () => {
61
+ assert.equal(propValue(null), '');
62
+ assert.equal(propValue(undefined), '');
63
+ });
64
+
65
+ it('handles title type', () => {
66
+ const prop = { type: 'title', title: [{ plain_text: 'My Title' }] };
67
+ assert.equal(propValue(prop), 'My Title');
68
+ });
69
+
70
+ it('handles rich_text type', () => {
71
+ const prop = { type: 'rich_text', rich_text: [{ plain_text: 'Some text' }] };
72
+ assert.equal(propValue(prop), 'Some text');
73
+ });
74
+
75
+ it('handles number type', () => {
76
+ assert.equal(propValue({ type: 'number', number: 42 }), '42');
77
+ assert.equal(propValue({ type: 'number', number: 0 }), '0');
78
+ assert.equal(propValue({ type: 'number', number: null }), '');
79
+ });
80
+
81
+ it('handles select type', () => {
82
+ assert.equal(propValue({ type: 'select', select: { name: 'Option A' } }), 'Option A');
83
+ assert.equal(propValue({ type: 'select', select: null }), '');
84
+ });
85
+
86
+ it('handles multi_select type', () => {
87
+ const prop = {
88
+ type: 'multi_select',
89
+ multi_select: [{ name: 'Tag1' }, { name: 'Tag2' }],
90
+ };
91
+ assert.equal(propValue(prop), 'Tag1, Tag2');
92
+ assert.equal(propValue({ type: 'multi_select', multi_select: [] }), '');
93
+ });
94
+
95
+ it('handles date type with start only', () => {
96
+ const prop = { type: 'date', date: { start: '2024-01-15' } };
97
+ assert.equal(propValue(prop), '2024-01-15');
98
+ });
99
+
100
+ it('handles date type with start and end', () => {
101
+ const prop = { type: 'date', date: { start: '2024-01-15', end: '2024-01-20' } };
102
+ assert.equal(propValue(prop), '2024-01-15 → 2024-01-20');
103
+ });
104
+
105
+ it('handles date type with null date', () => {
106
+ assert.equal(propValue({ type: 'date', date: null }), '');
107
+ });
108
+
109
+ it('handles checkbox type', () => {
110
+ assert.equal(propValue({ type: 'checkbox', checkbox: true }), '✓');
111
+ assert.equal(propValue({ type: 'checkbox', checkbox: false }), '✗');
112
+ });
113
+
114
+ it('handles url type', () => {
115
+ assert.equal(propValue({ type: 'url', url: 'https://example.com' }), 'https://example.com');
116
+ assert.equal(propValue({ type: 'url', url: null }), '');
117
+ });
118
+
119
+ it('handles email type', () => {
120
+ assert.equal(propValue({ type: 'email', email: 'test@example.com' }), 'test@example.com');
121
+ assert.equal(propValue({ type: 'email', email: null }), '');
122
+ });
123
+
124
+ it('handles phone_number type', () => {
125
+ assert.equal(propValue({ type: 'phone_number', phone_number: '+1234567890' }), '+1234567890');
126
+ assert.equal(propValue({ type: 'phone_number', phone_number: null }), '');
127
+ });
128
+
129
+ it('handles status type', () => {
130
+ assert.equal(propValue({ type: 'status', status: { name: 'In Progress' } }), 'In Progress');
131
+ assert.equal(propValue({ type: 'status', status: null }), '');
132
+ });
133
+
134
+ it('handles formula type — string', () => {
135
+ assert.equal(propValue({ type: 'formula', formula: { string: 'computed' } }), 'computed');
136
+ });
137
+
138
+ it('handles formula type — number', () => {
139
+ assert.equal(propValue({ type: 'formula', formula: { number: 99 } }), '99');
140
+ });
141
+
142
+ it('handles formula type — boolean', () => {
143
+ assert.equal(propValue({ type: 'formula', formula: { boolean: true } }), 'true');
144
+ });
145
+
146
+ it('handles formula type — date', () => {
147
+ assert.equal(propValue({ type: 'formula', formula: { date: { start: '2024-06-01' } } }), '2024-06-01');
148
+ });
149
+
150
+ it('handles formula type — null', () => {
151
+ assert.equal(propValue({ type: 'formula', formula: null }), '');
152
+ });
153
+
154
+ it('handles relation type', () => {
155
+ const prop = { type: 'relation', relation: [{ id: 'abc' }, { id: 'def' }] };
156
+ assert.equal(propValue(prop), 'abc, def');
157
+ assert.equal(propValue({ type: 'relation', relation: [] }), '');
158
+ });
159
+
160
+ it('handles rollup type', () => {
161
+ const rollupData = { type: 'number', number: 100 };
162
+ assert.equal(propValue({ type: 'rollup', rollup: rollupData }), JSON.stringify(rollupData));
163
+ });
164
+
165
+ it('handles people type', () => {
166
+ const prop = {
167
+ type: 'people',
168
+ people: [{ name: 'Alice' }, { id: 'user-id-123' }],
169
+ };
170
+ assert.equal(propValue(prop), 'Alice, user-id-123');
171
+ });
172
+
173
+ it('handles files type', () => {
174
+ const prop = {
175
+ type: 'files',
176
+ files: [
177
+ { name: 'doc.pdf' },
178
+ { external: { url: 'https://example.com/file.png' } },
179
+ ],
180
+ };
181
+ assert.equal(propValue(prop), 'doc.pdf, https://example.com/file.png');
182
+ });
183
+
184
+ it('handles created_time type', () => {
185
+ assert.equal(propValue({ type: 'created_time', created_time: '2024-01-01T00:00:00Z' }), '2024-01-01T00:00:00Z');
186
+ assert.equal(propValue({ type: 'created_time', created_time: '' }), '');
187
+ });
188
+
189
+ it('handles last_edited_time type', () => {
190
+ assert.equal(propValue({ type: 'last_edited_time', last_edited_time: '2024-06-01T12:00:00Z' }), '2024-06-01T12:00:00Z');
191
+ });
192
+
193
+ it('handles created_by type', () => {
194
+ assert.equal(propValue({ type: 'created_by', created_by: { name: 'Bob' } }), 'Bob');
195
+ assert.equal(propValue({ type: 'created_by', created_by: { id: 'uid' } }), 'uid');
196
+ assert.equal(propValue({ type: 'created_by', created_by: null }), '');
197
+ });
198
+
199
+ it('handles last_edited_by type', () => {
200
+ assert.equal(propValue({ type: 'last_edited_by', last_edited_by: { name: 'Carol' } }), 'Carol');
201
+ });
202
+
203
+ it('handles unknown type — JSON stringified', () => {
204
+ assert.equal(propValue({ type: 'custom_thing', custom_thing: { foo: 'bar' } }), '{"foo":"bar"}');
205
+ });
206
+
207
+ it('handles unknown type with null value', () => {
208
+ // null ?? '' → '', JSON.stringify('') → '""'
209
+ assert.equal(propValue({ type: 'mystery', mystery: null }), '""');
210
+ });
211
+
212
+ it('handles unknown type with undefined property', () => {
213
+ assert.equal(propValue({ type: 'missing_prop' }), '""');
214
+ });
215
+ });
216
+
217
+ // ─── buildPropValue ────────────────────────────────────────────────────────────
218
+
219
+ describe('buildPropValue', () => {
220
+ it('builds title property', () => {
221
+ assert.deepEqual(buildPropValue('title', 'Hello'), {
222
+ title: [{ text: { content: 'Hello' } }],
223
+ });
224
+ });
225
+
226
+ it('builds rich_text property', () => {
227
+ assert.deepEqual(buildPropValue('rich_text', 'Some text'), {
228
+ rich_text: [{ text: { content: 'Some text' } }],
229
+ });
230
+ });
231
+
232
+ it('builds number property', () => {
233
+ assert.deepEqual(buildPropValue('number', '42'), { number: 42 });
234
+ assert.deepEqual(buildPropValue('number', '3.14'), { number: 3.14 });
235
+ });
236
+
237
+ it('builds select property', () => {
238
+ assert.deepEqual(buildPropValue('select', 'Option A'), {
239
+ select: { name: 'Option A' },
240
+ });
241
+ });
242
+
243
+ it('builds multi_select property with commas', () => {
244
+ assert.deepEqual(buildPropValue('multi_select', 'Tag1, Tag2, Tag3'), {
245
+ multi_select: [{ name: 'Tag1' }, { name: 'Tag2' }, { name: 'Tag3' }],
246
+ });
247
+ });
248
+
249
+ it('builds multi_select property with single value', () => {
250
+ assert.deepEqual(buildPropValue('multi_select', 'OnlyTag'), {
251
+ multi_select: [{ name: 'OnlyTag' }],
252
+ });
253
+ });
254
+
255
+ it('builds date property', () => {
256
+ assert.deepEqual(buildPropValue('date', '2024-01-15'), {
257
+ date: { start: '2024-01-15' },
258
+ });
259
+ });
260
+
261
+ it('builds checkbox property — true values', () => {
262
+ assert.deepEqual(buildPropValue('checkbox', 'true'), { checkbox: true });
263
+ assert.deepEqual(buildPropValue('checkbox', '1'), { checkbox: true });
264
+ assert.deepEqual(buildPropValue('checkbox', 'yes'), { checkbox: true });
265
+ });
266
+
267
+ it('builds checkbox property — false values', () => {
268
+ assert.deepEqual(buildPropValue('checkbox', 'false'), { checkbox: false });
269
+ assert.deepEqual(buildPropValue('checkbox', '0'), { checkbox: false });
270
+ assert.deepEqual(buildPropValue('checkbox', 'no'), { checkbox: false });
271
+ assert.deepEqual(buildPropValue('checkbox', 'anything'), { checkbox: false });
272
+ });
273
+
274
+ it('builds url property', () => {
275
+ assert.deepEqual(buildPropValue('url', 'https://example.com'), {
276
+ url: 'https://example.com',
277
+ });
278
+ });
279
+
280
+ it('builds email property', () => {
281
+ assert.deepEqual(buildPropValue('email', 'user@test.com'), {
282
+ email: 'user@test.com',
283
+ });
284
+ });
285
+
286
+ it('builds phone_number property', () => {
287
+ assert.deepEqual(buildPropValue('phone_number', '+1234567890'), {
288
+ phone_number: '+1234567890',
289
+ });
290
+ });
291
+
292
+ it('builds status property', () => {
293
+ assert.deepEqual(buildPropValue('status', 'Done'), {
294
+ status: { name: 'Done' },
295
+ });
296
+ });
297
+
298
+ it('builds unknown type — raw passthrough', () => {
299
+ assert.deepEqual(buildPropValue('custom_type', 'raw'), {
300
+ custom_type: 'raw',
301
+ });
302
+ });
303
+ });
304
+
305
+ // ─── printTable ────────────────────────────────────────────────────────────────
306
+
307
+ describe('printTable', () => {
308
+ // Helper to capture stdout
309
+ function captureLog(fn) {
310
+ const lines = [];
311
+ const orig = console.log;
312
+ console.log = (...args) => lines.push(args.join(' '));
313
+ try {
314
+ fn();
315
+ } finally {
316
+ console.log = orig;
317
+ }
318
+ return lines;
319
+ }
320
+
321
+ it('prints (no results) for empty rows', () => {
322
+ const lines = captureLog(() => printTable([], ['col']));
323
+ assert.equal(lines.length, 1);
324
+ assert.equal(lines[0], '(no results)');
325
+ });
326
+
327
+ it('prints (no results) for null rows', () => {
328
+ const lines = captureLog(() => printTable(null, ['col']));
329
+ assert.equal(lines[0], '(no results)');
330
+ });
331
+
332
+ it('prints header, separator, data rows, and count', () => {
333
+ const rows = [{ name: 'Alice', age: '30' }];
334
+ const lines = captureLog(() => printTable(rows, ['name', 'age']));
335
+ // header, separator, 1 data row, blank + count
336
+ assert.ok(lines[0].includes('name'));
337
+ assert.ok(lines[0].includes('age'));
338
+ assert.ok(lines[1].includes('─'));
339
+ assert.ok(lines[2].includes('Alice'));
340
+ assert.ok(lines[2].includes('30'));
341
+ assert.ok(lines[3].includes('1 result'));
342
+ });
343
+
344
+ it('pluralizes result count', () => {
345
+ const rows = [{ x: 'a' }, { x: 'b' }];
346
+ const lines = captureLog(() => printTable(rows, ['x']));
347
+ assert.ok(lines[lines.length - 1].includes('2 results'));
348
+ });
349
+
350
+ it('truncates values longer than 50 chars', () => {
351
+ const longVal = 'A'.repeat(60);
352
+ const rows = [{ col: longVal }];
353
+ const lines = captureLog(() => printTable(rows, ['col']));
354
+ const dataLine = lines[2];
355
+ assert.ok(dataLine.includes('...'));
356
+ assert.ok(!dataLine.includes(longVal));
357
+ });
358
+
359
+ it('caps column width at 50', () => {
360
+ const longVal = 'B'.repeat(100);
361
+ const rows = [{ col: longVal }];
362
+ const lines = captureLog(() => printTable(rows, ['col']));
363
+ // The separator line should have exactly 50 dashes for the column
364
+ const sepParts = lines[1].split('─┼─');
365
+ // Single column so no split, just dashes
366
+ assert.ok(lines[1].length <= 55); // 50 + some padding
367
+ });
368
+ });
369
+
370
+ // ─── pagesToRows ───────────────────────────────────────────────────────────────
371
+
372
+ describe('pagesToRows', () => {
373
+ it('extracts id and all properties', () => {
374
+ const pages = [{
375
+ id: 'page-123',
376
+ properties: {
377
+ Name: { type: 'title', title: [{ plain_text: 'Test' }] },
378
+ Status: { type: 'select', select: { name: 'Active' } },
379
+ },
380
+ }];
381
+ const rows = pagesToRows(pages);
382
+ assert.equal(rows.length, 1);
383
+ assert.equal(rows[0].id, 'page-123');
384
+ assert.equal(rows[0].Name, 'Test');
385
+ assert.equal(rows[0].Status, 'Active');
386
+ });
387
+
388
+ it('handles pages with no properties', () => {
389
+ const pages = [{ id: 'page-456' }];
390
+ const rows = pagesToRows(pages);
391
+ assert.equal(rows.length, 1);
392
+ assert.equal(rows[0].id, 'page-456');
393
+ assert.equal(Object.keys(rows[0]).length, 1);
394
+ });
395
+
396
+ it('handles empty pages array', () => {
397
+ assert.deepEqual(pagesToRows([]), []);
398
+ });
399
+
400
+ it('handles multiple pages', () => {
401
+ const pages = [
402
+ { id: 'a', properties: { X: { type: 'number', number: 1 } } },
403
+ { id: 'b', properties: { X: { type: 'number', number: 2 } } },
404
+ ];
405
+ const rows = pagesToRows(pages);
406
+ assert.equal(rows.length, 2);
407
+ assert.equal(rows[0].X, '1');
408
+ assert.equal(rows[1].X, '2');
409
+ });
410
+ });
411
+
412
+ // ─── formatCsv ─────────────────────────────────────────────────────────────────
413
+
414
+ describe('formatCsv', () => {
415
+ it('returns (no results) for empty rows', () => {
416
+ assert.equal(formatCsv([], ['a']), '(no results)');
417
+ assert.equal(formatCsv(null, ['a']), '(no results)');
418
+ });
419
+
420
+ it('produces header row', () => {
421
+ const rows = [{ name: 'Alice', age: '30' }];
422
+ const csv = formatCsv(rows, ['name', 'age']);
423
+ const lines = csv.split('\n');
424
+ assert.equal(lines[0], 'name,age');
425
+ });
426
+
427
+ it('produces data rows', () => {
428
+ const rows = [{ name: 'Alice', age: '30' }];
429
+ const csv = formatCsv(rows, ['name', 'age']);
430
+ const lines = csv.split('\n');
431
+ assert.equal(lines[1], 'Alice,30');
432
+ });
433
+
434
+ it('quotes values with commas', () => {
435
+ const rows = [{ val: 'a, b' }];
436
+ const csv = formatCsv(rows, ['val']);
437
+ const lines = csv.split('\n');
438
+ assert.equal(lines[1], '"a, b"');
439
+ });
440
+
441
+ it('escapes double quotes', () => {
442
+ const rows = [{ val: 'say "hello"' }];
443
+ const csv = formatCsv(rows, ['val']);
444
+ const lines = csv.split('\n');
445
+ assert.equal(lines[1], '"say ""hello"""');
446
+ });
447
+
448
+ it('quotes values with newlines', () => {
449
+ const rows = [{ val: 'line1\nline2' }];
450
+ const csv = formatCsv(rows, ['val']);
451
+ assert.ok(csv.includes('"line1\nline2"'));
452
+ });
453
+
454
+ it('handles null/undefined values', () => {
455
+ const rows = [{ a: null, b: undefined }];
456
+ const csv = formatCsv(rows, ['a', 'b']);
457
+ const lines = csv.split('\n');
458
+ assert.equal(lines[1], ',');
459
+ });
460
+ });
461
+
462
+ // ─── formatYaml ────────────────────────────────────────────────────────────────
463
+
464
+ describe('formatYaml', () => {
465
+ it('returns (no results) for empty rows', () => {
466
+ assert.equal(formatYaml([], ['a']), '(no results)');
467
+ assert.equal(formatYaml(null, ['a']), '(no results)');
468
+ });
469
+
470
+ it('produces correct key: value format', () => {
471
+ const rows = [{ name: 'Alice', status: 'Active' }];
472
+ const yaml = formatYaml(rows, ['name', 'status']);
473
+ assert.ok(yaml.includes('- # result 1'));
474
+ assert.ok(yaml.includes(' name: Alice'));
475
+ assert.ok(yaml.includes(' status: Active'));
476
+ });
477
+
478
+ it('quotes values with special YAML characters', () => {
479
+ const rows = [{ val: 'key: value' }];
480
+ const yaml = formatYaml(rows, ['val']);
481
+ assert.ok(yaml.includes('"key: value"'));
482
+ });
483
+
484
+ it('quotes empty strings', () => {
485
+ const rows = [{ val: '' }];
486
+ const yaml = formatYaml(rows, ['val']);
487
+ assert.ok(yaml.includes('val: ""'));
488
+ });
489
+
490
+ it('separates multiple results with blank lines', () => {
491
+ const rows = [{ x: 'a' }, { x: 'b' }];
492
+ const yaml = formatYaml(rows, ['x']);
493
+ assert.ok(yaml.includes('- # result 1'));
494
+ assert.ok(yaml.includes('- # result 2'));
495
+ const lines = yaml.split('\n');
496
+ // There should be a blank line between result groups
497
+ assert.ok(lines.some(l => l === ''));
498
+ });
499
+
500
+ it('escapes double quotes inside values', () => {
501
+ const rows = [{ val: 'say "hi"' }];
502
+ const yaml = formatYaml(rows, ['val']);
503
+ assert.ok(yaml.includes('\\"hi\\"'));
504
+ });
505
+ });
506
+
507
+ // ─── UUID_REGEX ────────────────────────────────────────────────────────────────
508
+
509
+ describe('UUID_REGEX', () => {
510
+ it('matches standard UUID with dashes', () => {
511
+ assert.ok(UUID_REGEX.test('550e8400-e29b-41d4-a716-446655440000'));
512
+ });
513
+
514
+ it('matches UUID without dashes (32 hex chars)', () => {
515
+ assert.ok(UUID_REGEX.test('550e8400e29b41d4a716446655440000'));
516
+ });
517
+
518
+ it('matches uppercase UUIDs', () => {
519
+ assert.ok(UUID_REGEX.test('550E8400-E29B-41D4-A716-446655440000'));
520
+ });
521
+
522
+ it('matches mixed case UUIDs', () => {
523
+ assert.ok(UUID_REGEX.test('550e8400-E29B-41d4-A716-446655440000'));
524
+ });
525
+
526
+ it('rejects short strings', () => {
527
+ assert.ok(!UUID_REGEX.test('abc123'));
528
+ assert.ok(!UUID_REGEX.test(''));
529
+ });
530
+
531
+ it('rejects strings with invalid characters', () => {
532
+ assert.ok(!UUID_REGEX.test('550e8400-e29b-41d4-a716-44665544000g'));
533
+ assert.ok(!UUID_REGEX.test('hello-world-this-is-not-a-uuid!'));
534
+ });
535
+
536
+ it('rejects alias-like strings', () => {
537
+ assert.ok(!UUID_REGEX.test('my-database'));
538
+ assert.ok(!UUID_REGEX.test('workouts'));
539
+ assert.ok(!UUID_REGEX.test('projects'));
540
+ });
541
+ });
542
+
543
+ // ─── buildFilterFromSchema ─────────────────────────────────────────────────────
544
+
545
+ describe('buildFilterFromSchema', () => {
546
+ const schema = {
547
+ name: { type: 'title', name: 'Name' },
548
+ description: { type: 'rich_text', name: 'Description' },
549
+ status: { type: 'select', name: 'Status' },
550
+ tags: { type: 'multi_select', name: 'Tags' },
551
+ count: { type: 'number', name: 'Count' },
552
+ done: { type: 'checkbox', name: 'Done' },
553
+ due: { type: 'date', name: 'Due' },
554
+ stage: { type: 'status', name: 'Stage' },
555
+ };
556
+
557
+ it('returns error for invalid filter format (no =)', () => {
558
+ const result = buildFilterFromSchema(schema, 'invalid');
559
+ assert.ok(result.error);
560
+ assert.ok(result.error.includes('Invalid filter format'));
561
+ });
562
+
563
+ it('returns error for unknown property', () => {
564
+ const result = buildFilterFromSchema(schema, 'nonexistent=value');
565
+ assert.ok(result.error);
566
+ assert.ok(result.available);
567
+ assert.ok(result.available.length > 0);
568
+ });
569
+
570
+ it('builds title filter with contains', () => {
571
+ const result = buildFilterFromSchema(schema, 'Name=Hello');
572
+ assert.deepEqual(result.filter, {
573
+ property: 'Name',
574
+ title: { contains: 'Hello' },
575
+ });
576
+ });
577
+
578
+ it('builds rich_text filter with contains', () => {
579
+ const result = buildFilterFromSchema(schema, 'Description=test');
580
+ assert.deepEqual(result.filter, {
581
+ property: 'Description',
582
+ rich_text: { contains: 'test' },
583
+ });
584
+ });
585
+
586
+ it('builds select filter with equals', () => {
587
+ const result = buildFilterFromSchema(schema, 'Status=Active');
588
+ assert.deepEqual(result.filter, {
589
+ property: 'Status',
590
+ select: { equals: 'Active' },
591
+ });
592
+ });
593
+
594
+ it('builds multi_select filter with contains', () => {
595
+ const result = buildFilterFromSchema(schema, 'Tags=Important');
596
+ assert.deepEqual(result.filter, {
597
+ property: 'Tags',
598
+ multi_select: { contains: 'Important' },
599
+ });
600
+ });
601
+
602
+ it('builds number filter with equals', () => {
603
+ const result = buildFilterFromSchema(schema, 'Count=42');
604
+ assert.deepEqual(result.filter, {
605
+ property: 'Count',
606
+ number: { equals: 42 },
607
+ });
608
+ });
609
+
610
+ it('builds checkbox filter — true', () => {
611
+ const result = buildFilterFromSchema(schema, 'Done=true');
612
+ assert.deepEqual(result.filter, {
613
+ property: 'Done',
614
+ checkbox: { equals: true },
615
+ });
616
+ });
617
+
618
+ it('builds checkbox filter — 1', () => {
619
+ const result = buildFilterFromSchema(schema, 'Done=1');
620
+ assert.deepEqual(result.filter, {
621
+ property: 'Done',
622
+ checkbox: { equals: true },
623
+ });
624
+ });
625
+
626
+ it('builds checkbox filter — false', () => {
627
+ const result = buildFilterFromSchema(schema, 'Done=false');
628
+ assert.deepEqual(result.filter, {
629
+ property: 'Done',
630
+ checkbox: { equals: false },
631
+ });
632
+ });
633
+
634
+ it('builds date filter with equals', () => {
635
+ const result = buildFilterFromSchema(schema, 'Due=2024-01-15');
636
+ assert.deepEqual(result.filter, {
637
+ property: 'Due',
638
+ date: { equals: '2024-01-15' },
639
+ });
640
+ });
641
+
642
+ it('builds status filter with equals', () => {
643
+ const result = buildFilterFromSchema(schema, 'Stage=In Progress');
644
+ assert.deepEqual(result.filter, {
645
+ property: 'Stage',
646
+ status: { equals: 'In Progress' },
647
+ });
648
+ });
649
+
650
+ it('is case-insensitive for property lookup', () => {
651
+ const result = buildFilterFromSchema(schema, 'NAME=test');
652
+ assert.ok(!result.error);
653
+ assert.equal(result.filter.property, 'Name');
654
+ });
655
+
656
+ it('handles values containing = signs', () => {
657
+ const result = buildFilterFromSchema(schema, 'Name=a=b=c');
658
+ assert.deepEqual(result.filter, {
659
+ property: 'Name',
660
+ title: { contains: 'a=b=c' },
661
+ });
662
+ });
663
+ });