@nymphjs/query-parser 1.0.0-beta.122 → 1.0.0-beta.124

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.
@@ -3,6 +3,13 @@ import queryParser from './queryParser.js';
3
3
 
4
4
  const BlogPost = function () {} as unknown as typeof Entity;
5
5
  const Category = function () {} as unknown as typeof Entity;
6
+ const List = function () {} as unknown as typeof Entity;
7
+
8
+ const Mail = function () {} as unknown as typeof Entity;
9
+ const Headers = function () {} as unknown as typeof Entity;
10
+ const Body = function () {} as unknown as typeof Entity;
11
+ const Label = function () {} as unknown as typeof Entity;
12
+ const Domain = function () {} as unknown as typeof Entity;
6
13
 
7
14
  describe('queryParser', () => {
8
15
  it('parses a basic query', () => {
@@ -55,13 +62,13 @@ describe('queryParser', () => {
55
62
  });
56
63
 
57
64
  it('gives the correct entity and default fields to bare queries', () => {
58
- const query = 'category<{cat search}>';
65
+ const query = 'category<{Cat search}>';
59
66
  const [options, ...selectors] = queryParser({
60
67
  query,
61
68
  entityClass: BlogPost,
62
69
  defaultFields: ['text'],
63
70
  qrefMap: {
64
- cat: {
71
+ Cat: {
65
72
  class: Category,
66
73
  defaultFields: ['name'],
67
74
  },
@@ -97,13 +104,13 @@ describe('queryParser', () => {
97
104
 
98
105
  it('parses the example query from the readme', () => {
99
106
  const query =
100
- 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{cat Tech}>';
107
+ 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{Cat Tech}>';
101
108
  const [options, ...selectors] = queryParser({
102
109
  query,
103
110
  entityClass: BlogPost,
104
111
  defaultFields: ['title', 'body'],
105
112
  qrefMap: {
106
- cat: {
113
+ Cat: {
107
114
  class: Category,
108
115
  defaultFields: ['name'],
109
116
  },
@@ -289,6 +296,108 @@ describe('queryParser', () => {
289
296
  ]);
290
297
  });
291
298
 
299
+ it('parses a query with not search', () => {
300
+ const query = 'limit:4 foobar -prop(a search string)';
301
+ const [options, ...selectors] = queryParser({
302
+ query,
303
+ entityClass: BlogPost,
304
+ defaultFields: ['title', 'body'],
305
+ qrefMap: {
306
+ cat: {
307
+ class: Category,
308
+ defaultFields: ['name'],
309
+ },
310
+ },
311
+ });
312
+
313
+ expect(options).toEqual({
314
+ class: BlogPost,
315
+ limit: 4,
316
+ });
317
+
318
+ expect(selectors).toEqual([
319
+ {
320
+ type: '&',
321
+ '!search': [['prop', 'a search string']],
322
+ },
323
+ {
324
+ type: '|',
325
+ ilike: [
326
+ ['title', '%foobar%'],
327
+ ['body', '%foobar%'],
328
+ ],
329
+ },
330
+ ]);
331
+ });
332
+
333
+ it('parses a query with colon search', () => {
334
+ const query = 'limit:4 foobar prop:string';
335
+ const [options, ...selectors] = queryParser({
336
+ query,
337
+ entityClass: BlogPost,
338
+ defaultFields: ['title', 'body'],
339
+ qrefMap: {
340
+ cat: {
341
+ class: Category,
342
+ defaultFields: ['name'],
343
+ },
344
+ },
345
+ });
346
+
347
+ expect(options).toEqual({
348
+ class: BlogPost,
349
+ limit: 4,
350
+ });
351
+
352
+ expect(selectors).toEqual([
353
+ {
354
+ type: '&',
355
+ search: [['prop', 'string']],
356
+ },
357
+ {
358
+ type: '|',
359
+ ilike: [
360
+ ['title', '%foobar%'],
361
+ ['body', '%foobar%'],
362
+ ],
363
+ },
364
+ ]);
365
+ });
366
+
367
+ it('parses a query with not colon search', () => {
368
+ const query = 'limit:4 foobar -prop:string';
369
+ const [options, ...selectors] = queryParser({
370
+ query,
371
+ entityClass: BlogPost,
372
+ defaultFields: ['title', 'body'],
373
+ qrefMap: {
374
+ cat: {
375
+ class: Category,
376
+ defaultFields: ['name'],
377
+ },
378
+ },
379
+ });
380
+
381
+ expect(options).toEqual({
382
+ class: BlogPost,
383
+ limit: 4,
384
+ });
385
+
386
+ expect(selectors).toEqual([
387
+ {
388
+ type: '&',
389
+ '!search': [['prop', 'string']],
390
+ },
391
+ {
392
+ type: '|',
393
+ ilike: [
394
+ ['title', '%foobar%'],
395
+ ['body', '%foobar%'],
396
+ ],
397
+ },
398
+ ]);
399
+ });
400
+
292
401
  it('parses a query with search in a selector', () => {
293
402
  const query =
294
403
  'limit:4 foobar (| prop(a search string) prop2("another search string"))';
@@ -332,6 +441,53 @@ describe('queryParser', () => {
332
441
  ]);
333
442
  });
334
443
 
444
+ it('parses a query with colon search in a selector', () => {
445
+ const query =
446
+ 'limit:4 foobar (| prop:string prop2:"another search string" -prop3:pickle -prop4:\'another -pickle string\')';
447
+ const [options, ...selectors] = queryParser({
448
+ query,
449
+ entityClass: BlogPost,
450
+ defaultFields: ['title', 'body'],
451
+ qrefMap: {
452
+ cat: {
453
+ class: Category,
454
+ defaultFields: ['name'],
455
+ },
456
+ },
457
+ });
458
+
459
+ expect(options).toEqual({
460
+ class: BlogPost,
461
+ limit: 4,
462
+ });
463
+
464
+ expect(selectors).toEqual([
465
+ {
466
+ type: '&',
467
+ selector: [
468
+ {
469
+ type: '|',
470
+ search: [
471
+ ['prop', 'string'],
472
+ ['prop2', '"another search string"'],
473
+ ],
474
+ '!search': [
475
+ ['prop3', 'pickle'],
476
+ ['prop4', "'another -pickle string'"],
477
+ ],
478
+ },
479
+ ],
480
+ },
481
+ {
482
+ type: '|',
483
+ ilike: [
484
+ ['title', '%foobar%'],
485
+ ['body', '%foobar%'],
486
+ ],
487
+ },
488
+ ]);
489
+ });
490
+
335
491
  it('parses a query with lt and tag', () => {
336
492
  const query = 'limit:4 foobar mdate<"2 weeks ago" <tag>';
337
493
  const [options, ...selectors] = queryParser({
@@ -537,7 +693,7 @@ describe('queryParser', () => {
537
693
  });
538
694
 
539
695
  it('parses simple not equal clauses', () => {
540
- const query = 'prop!=string';
696
+ const query = '-prop=string';
541
697
  const [options, ...selectors] = queryParser({
542
698
  query,
543
699
  entityClass: BlogPost,
@@ -575,7 +731,7 @@ describe('queryParser', () => {
575
731
  });
576
732
 
577
733
  it('parses quoted not equal clauses', () => {
578
- const query = 'prop!="a string"';
734
+ const query = '-prop="a string"';
579
735
  const [options, ...selectors] = queryParser({
580
736
  query,
581
737
  entityClass: BlogPost,
@@ -613,7 +769,7 @@ describe('queryParser', () => {
613
769
  });
614
770
 
615
771
  it('parses json not equal clauses', () => {
616
- const query = 'prop!=true';
772
+ const query = '-prop=true';
617
773
  const [options, ...selectors] = queryParser({
618
774
  query,
619
775
  entityClass: BlogPost,
@@ -651,7 +807,7 @@ describe('queryParser', () => {
651
807
  });
652
808
 
653
809
  it('parses not guid clauses', () => {
654
- const query = '{!111111111111111111111111}';
810
+ const query = '-{111111111111111111111111}';
655
811
  const [options, ...selectors] = queryParser({
656
812
  query,
657
813
  entityClass: BlogPost,
@@ -689,7 +845,7 @@ describe('queryParser', () => {
689
845
  });
690
846
 
691
847
  it('parses not tag clauses', () => {
692
- const query = '<!tagname>';
848
+ const query = '-<tagname>';
693
849
  const [options, ...selectors] = queryParser({
694
850
  query,
695
851
  entityClass: BlogPost,
@@ -727,7 +883,7 @@ describe('queryParser', () => {
727
883
  });
728
884
 
729
885
  it('parses not truthy clauses', () => {
730
- const query = '[!truthyname]';
886
+ const query = '-[truthyname]';
731
887
  const [options, ...selectors] = queryParser({
732
888
  query,
733
889
  entityClass: BlogPost,
@@ -765,7 +921,7 @@ describe('queryParser', () => {
765
921
  });
766
922
 
767
923
  it('parses not ref clauses', () => {
768
- const query = 'prop!<{111111111111111111111111}>';
924
+ const query = '-prop<{111111111111111111111111}>';
769
925
  const [options, ...selectors] = queryParser({
770
926
  query,
771
927
  entityClass: BlogPost,
@@ -803,7 +959,7 @@ describe('queryParser', () => {
803
959
  });
804
960
 
805
961
  it('parses simple not contain clauses', () => {
806
- const query = 'prop!<string>';
962
+ const query = '-prop<string>';
807
963
  const [options, ...selectors] = queryParser({
808
964
  query,
809
965
  entityClass: BlogPost,
@@ -841,7 +997,7 @@ describe('queryParser', () => {
841
997
  });
842
998
 
843
999
  it('parses json not contain clauses', () => {
844
- const query = 'prop!<true>';
1000
+ const query = '-prop<true>';
845
1001
  const [options, ...selectors] = queryParser({
846
1002
  query,
847
1003
  entityClass: BlogPost,
@@ -879,7 +1035,7 @@ describe('queryParser', () => {
879
1035
  });
880
1036
 
881
1037
  it('parses not match clauses', () => {
882
- const query = 'prop!~/regex/';
1038
+ const query = '-prop~/regex/';
883
1039
  const [options, ...selectors] = queryParser({
884
1040
  query,
885
1041
  entityClass: BlogPost,
@@ -917,7 +1073,7 @@ describe('queryParser', () => {
917
1073
  });
918
1074
 
919
1075
  it('parses not imatch clauses', () => {
920
- const query = 'prop!~/regex/i';
1076
+ const query = '-prop~/regex/i';
921
1077
  const [options, ...selectors] = queryParser({
922
1078
  query,
923
1079
  entityClass: BlogPost,
@@ -955,7 +1111,7 @@ describe('queryParser', () => {
955
1111
  });
956
1112
 
957
1113
  it('parses simple not like clauses', () => {
958
- const query = 'prop!~pattern';
1114
+ const query = '-prop~pattern';
959
1115
  const [options, ...selectors] = queryParser({
960
1116
  query,
961
1117
  entityClass: BlogPost,
@@ -993,7 +1149,7 @@ describe('queryParser', () => {
993
1149
  });
994
1150
 
995
1151
  it('parses quoted not like clauses', () => {
996
- const query = 'prop!~"a pattern"';
1152
+ const query = '-prop~"a pattern"';
997
1153
  const [options, ...selectors] = queryParser({
998
1154
  query,
999
1155
  entityClass: BlogPost,
@@ -1031,7 +1187,7 @@ describe('queryParser', () => {
1031
1187
  });
1032
1188
 
1033
1189
  it('parses not ilike clauses', () => {
1034
- const query = 'prop!~"a pattern"i';
1190
+ const query = '-prop~"a pattern"i';
1035
1191
  const [options, ...selectors] = queryParser({
1036
1192
  query,
1037
1193
  entityClass: BlogPost,
@@ -1068,6 +1224,25 @@ describe('queryParser', () => {
1068
1224
  ]);
1069
1225
  });
1070
1226
 
1227
+ it('parses simple not gt clauses', () => {
1228
+ const query = '-prop>30.5';
1229
+ const [options, ...selectors] = queryParser({
1230
+ query,
1231
+ entityClass: BlogPost,
1232
+ });
1233
+
1234
+ expect(options).toEqual({
1235
+ class: BlogPost,
1236
+ });
1237
+
1238
+ expect(selectors).toEqual([
1239
+ {
1240
+ type: '&',
1241
+ '!gt': [['prop', 30.5]],
1242
+ },
1243
+ ]);
1244
+ });
1245
+
1071
1246
  it('parses simple relative gt clauses', () => {
1072
1247
  const query = 'prop>yesterday';
1073
1248
  const [options, ...selectors] = queryParser({
@@ -1087,6 +1262,25 @@ describe('queryParser', () => {
1087
1262
  ]);
1088
1263
  });
1089
1264
 
1265
+ it('parses simple relative not gt clauses', () => {
1266
+ const query = '-prop>yesterday';
1267
+ const [options, ...selectors] = queryParser({
1268
+ query,
1269
+ entityClass: BlogPost,
1270
+ });
1271
+
1272
+ expect(options).toEqual({
1273
+ class: BlogPost,
1274
+ });
1275
+
1276
+ expect(selectors).toEqual([
1277
+ {
1278
+ type: '&',
1279
+ '!gt': [['prop', null, 'yesterday']],
1280
+ },
1281
+ ]);
1282
+ });
1283
+
1090
1284
  it('parses quote relative gt clauses', () => {
1091
1285
  const query = 'prop>"3 weeks ago"';
1092
1286
  const [options, ...selectors] = queryParser({
@@ -1106,6 +1300,25 @@ describe('queryParser', () => {
1106
1300
  ]);
1107
1301
  });
1108
1302
 
1303
+ it('parses quote relative not gt clauses', () => {
1304
+ const query = '-prop>"3 weeks ago"';
1305
+ const [options, ...selectors] = queryParser({
1306
+ query,
1307
+ entityClass: BlogPost,
1308
+ });
1309
+
1310
+ expect(options).toEqual({
1311
+ class: BlogPost,
1312
+ });
1313
+
1314
+ expect(selectors).toEqual([
1315
+ {
1316
+ type: '&',
1317
+ '!gt': [['prop', null, '3 weeks ago']],
1318
+ },
1319
+ ]);
1320
+ });
1321
+
1109
1322
  it('parses simple gte clauses', () => {
1110
1323
  const query = 'prop>=30.5';
1111
1324
  const [options, ...selectors] = queryParser({
@@ -1125,6 +1338,25 @@ describe('queryParser', () => {
1125
1338
  ]);
1126
1339
  });
1127
1340
 
1341
+ it('parses simple not gte clauses', () => {
1342
+ const query = '-prop>=30.5';
1343
+ const [options, ...selectors] = queryParser({
1344
+ query,
1345
+ entityClass: BlogPost,
1346
+ });
1347
+
1348
+ expect(options).toEqual({
1349
+ class: BlogPost,
1350
+ });
1351
+
1352
+ expect(selectors).toEqual([
1353
+ {
1354
+ type: '&',
1355
+ '!gte': [['prop', 30.5]],
1356
+ },
1357
+ ]);
1358
+ });
1359
+
1128
1360
  it('parses simple relative gte clauses', () => {
1129
1361
  const query = 'prop>=yesterday';
1130
1362
  const [options, ...selectors] = queryParser({
@@ -1144,6 +1376,25 @@ describe('queryParser', () => {
1144
1376
  ]);
1145
1377
  });
1146
1378
 
1379
+ it('parses simple relative not gte clauses', () => {
1380
+ const query = '-prop>=yesterday';
1381
+ const [options, ...selectors] = queryParser({
1382
+ query,
1383
+ entityClass: BlogPost,
1384
+ });
1385
+
1386
+ expect(options).toEqual({
1387
+ class: BlogPost,
1388
+ });
1389
+
1390
+ expect(selectors).toEqual([
1391
+ {
1392
+ type: '&',
1393
+ '!gte': [['prop', null, 'yesterday']],
1394
+ },
1395
+ ]);
1396
+ });
1397
+
1147
1398
  it('parses quote relative gte clauses', () => {
1148
1399
  const query = 'prop>="3 weeks ago"';
1149
1400
  const [options, ...selectors] = queryParser({
@@ -1163,6 +1414,25 @@ describe('queryParser', () => {
1163
1414
  ]);
1164
1415
  });
1165
1416
 
1417
+ it('parses quote relative not gte clauses', () => {
1418
+ const query = '-prop>="3 weeks ago"';
1419
+ const [options, ...selectors] = queryParser({
1420
+ query,
1421
+ entityClass: BlogPost,
1422
+ });
1423
+
1424
+ expect(options).toEqual({
1425
+ class: BlogPost,
1426
+ });
1427
+
1428
+ expect(selectors).toEqual([
1429
+ {
1430
+ type: '&',
1431
+ '!gte': [['prop', null, '3 weeks ago']],
1432
+ },
1433
+ ]);
1434
+ });
1435
+
1166
1436
  it('parses simple lt clauses', () => {
1167
1437
  const query = 'prop<30.5';
1168
1438
  const [options, ...selectors] = queryParser({
@@ -1182,6 +1452,25 @@ describe('queryParser', () => {
1182
1452
  ]);
1183
1453
  });
1184
1454
 
1455
+ it('parses simple not lt clauses', () => {
1456
+ const query = '-prop<30.5';
1457
+ const [options, ...selectors] = queryParser({
1458
+ query,
1459
+ entityClass: BlogPost,
1460
+ });
1461
+
1462
+ expect(options).toEqual({
1463
+ class: BlogPost,
1464
+ });
1465
+
1466
+ expect(selectors).toEqual([
1467
+ {
1468
+ type: '&',
1469
+ '!lt': [['prop', 30.5]],
1470
+ },
1471
+ ]);
1472
+ });
1473
+
1185
1474
  it('parses simple relative lt clauses', () => {
1186
1475
  const query = 'prop<yesterday';
1187
1476
  const [options, ...selectors] = queryParser({
@@ -1201,6 +1490,25 @@ describe('queryParser', () => {
1201
1490
  ]);
1202
1491
  });
1203
1492
 
1493
+ it('parses simple relative not lt clauses', () => {
1494
+ const query = '-prop<yesterday';
1495
+ const [options, ...selectors] = queryParser({
1496
+ query,
1497
+ entityClass: BlogPost,
1498
+ });
1499
+
1500
+ expect(options).toEqual({
1501
+ class: BlogPost,
1502
+ });
1503
+
1504
+ expect(selectors).toEqual([
1505
+ {
1506
+ type: '&',
1507
+ '!lt': [['prop', null, 'yesterday']],
1508
+ },
1509
+ ]);
1510
+ });
1511
+
1204
1512
  it('parses quote relative lt clauses', () => {
1205
1513
  const query = 'prop<"3 weeks ago"';
1206
1514
  const [options, ...selectors] = queryParser({
@@ -1220,6 +1528,25 @@ describe('queryParser', () => {
1220
1528
  ]);
1221
1529
  });
1222
1530
 
1531
+ it('parses quote relative not lt clauses', () => {
1532
+ const query = '-prop<"3 weeks ago"';
1533
+ const [options, ...selectors] = queryParser({
1534
+ query,
1535
+ entityClass: BlogPost,
1536
+ });
1537
+
1538
+ expect(options).toEqual({
1539
+ class: BlogPost,
1540
+ });
1541
+
1542
+ expect(selectors).toEqual([
1543
+ {
1544
+ type: '&',
1545
+ '!lt': [['prop', null, '3 weeks ago']],
1546
+ },
1547
+ ]);
1548
+ });
1549
+
1223
1550
  it('parses simple lte clauses', () => {
1224
1551
  const query = 'prop<=30.5';
1225
1552
  const [options, ...selectors] = queryParser({
@@ -1239,6 +1566,25 @@ describe('queryParser', () => {
1239
1566
  ]);
1240
1567
  });
1241
1568
 
1569
+ it('parses simple not lte clauses', () => {
1570
+ const query = '-prop<=30.5';
1571
+ const [options, ...selectors] = queryParser({
1572
+ query,
1573
+ entityClass: BlogPost,
1574
+ });
1575
+
1576
+ expect(options).toEqual({
1577
+ class: BlogPost,
1578
+ });
1579
+
1580
+ expect(selectors).toEqual([
1581
+ {
1582
+ type: '&',
1583
+ '!lte': [['prop', 30.5]],
1584
+ },
1585
+ ]);
1586
+ });
1587
+
1242
1588
  it('parses simple relative lte clauses', () => {
1243
1589
  const query = 'prop<=yesterday';
1244
1590
  const [options, ...selectors] = queryParser({
@@ -1258,6 +1604,25 @@ describe('queryParser', () => {
1258
1604
  ]);
1259
1605
  });
1260
1606
 
1607
+ it('parses simple relative not lte clauses', () => {
1608
+ const query = '-prop<=yesterday';
1609
+ const [options, ...selectors] = queryParser({
1610
+ query,
1611
+ entityClass: BlogPost,
1612
+ });
1613
+
1614
+ expect(options).toEqual({
1615
+ class: BlogPost,
1616
+ });
1617
+
1618
+ expect(selectors).toEqual([
1619
+ {
1620
+ type: '&',
1621
+ '!lte': [['prop', null, 'yesterday']],
1622
+ },
1623
+ ]);
1624
+ });
1625
+
1261
1626
  it('parses quote relative lte clauses', () => {
1262
1627
  const query = 'prop<="3 weeks ago"';
1263
1628
  const [options, ...selectors] = queryParser({
@@ -1277,9 +1642,28 @@ describe('queryParser', () => {
1277
1642
  ]);
1278
1643
  });
1279
1644
 
1645
+ it('parses quote relative not lte clauses', () => {
1646
+ const query = '-prop<="3 weeks ago"';
1647
+ const [options, ...selectors] = queryParser({
1648
+ query,
1649
+ entityClass: BlogPost,
1650
+ });
1651
+
1652
+ expect(options).toEqual({
1653
+ class: BlogPost,
1654
+ });
1655
+
1656
+ expect(selectors).toEqual([
1657
+ {
1658
+ type: '&',
1659
+ '!lte': [['prop', null, '3 weeks ago']],
1660
+ },
1661
+ ]);
1662
+ });
1663
+
1280
1664
  it('parses complex selectors', () => {
1281
1665
  const query =
1282
- '(! [published] <draft>) (| cdate>"6 months ago" mdate>"1 month ago")';
1666
+ '(! [published] <draft>) (| -cdate>"6 months ago" mdate>"1 month ago")';
1283
1667
  const [options, ...selectors] = queryParser({
1284
1668
  query,
1285
1669
  entityClass: BlogPost,
@@ -1295,10 +1679,8 @@ describe('queryParser', () => {
1295
1679
  selector: [
1296
1680
  {
1297
1681
  type: '|',
1298
- gt: [
1299
- ['cdate', null, '6 months ago'],
1300
- ['mdate', null, '1 month ago'],
1301
- ],
1682
+ '!gt': [['cdate', null, '6 months ago']],
1683
+ gt: [['mdate', null, '1 month ago']],
1302
1684
  },
1303
1685
  {
1304
1686
  type: '!&',
@@ -1312,7 +1694,7 @@ describe('queryParser', () => {
1312
1694
 
1313
1695
  it('parses nested selectors', () => {
1314
1696
  const query =
1315
- '(! [published] <draft> (| cdate>"6 months ago" mdate>"1 month ago"))';
1697
+ '(! [published] <draft> (| -cdate>"6 months ago" mdate>"1 month ago"))';
1316
1698
  const [options, ...selectors] = queryParser({
1317
1699
  query,
1318
1700
  entityClass: BlogPost,
@@ -1330,10 +1712,8 @@ describe('queryParser', () => {
1330
1712
  selector: [
1331
1713
  {
1332
1714
  type: '|',
1333
- gt: [
1334
- ['cdate', null, '6 months ago'],
1335
- ['mdate', null, '1 month ago'],
1336
- ],
1715
+ '!gt': [['cdate', null, '6 months ago']],
1716
+ gt: [['mdate', null, '1 month ago']],
1337
1717
  },
1338
1718
  ],
1339
1719
  },
@@ -1342,7 +1722,7 @@ describe('queryParser', () => {
1342
1722
 
1343
1723
  it('parses nested qref clauses', () => {
1344
1724
  const query =
1345
- 'categories<{Category parent<{Category id="sort+newsletters"}>}> ';
1725
+ 'categories<{Category parent<{Category list<{List frosting name(favorite)}>}> -parent<{Category id="sort+newsletters"}>}>';
1346
1726
  const [options, ...selectors] = queryParser({
1347
1727
  query,
1348
1728
  entityClass: BlogPost,
@@ -1352,6 +1732,10 @@ describe('queryParser', () => {
1352
1732
  class: Category,
1353
1733
  defaultFields: ['name'],
1354
1734
  },
1735
+ List: {
1736
+ class: List,
1737
+ defaultFields: ['name'],
1738
+ },
1355
1739
  },
1356
1740
  });
1357
1741
 
@@ -1370,6 +1754,33 @@ describe('queryParser', () => {
1370
1754
  {
1371
1755
  type: '&',
1372
1756
  qref: [
1757
+ [
1758
+ 'parent',
1759
+ [
1760
+ { class: Category },
1761
+ {
1762
+ type: '&',
1763
+ qref: [
1764
+ [
1765
+ 'list',
1766
+ [
1767
+ { class: List },
1768
+ {
1769
+ type: '&',
1770
+ search: [['name', 'favorite']],
1771
+ },
1772
+ {
1773
+ type: '|',
1774
+ ilike: [['name', '%frosting%']],
1775
+ },
1776
+ ],
1777
+ ],
1778
+ ],
1779
+ },
1780
+ ],
1781
+ ],
1782
+ ],
1783
+ '!qref': [
1373
1784
  [
1374
1785
  'parent',
1375
1786
  [
@@ -1388,4 +1799,142 @@ describe('queryParser', () => {
1388
1799
  },
1389
1800
  ]);
1390
1801
  });
1802
+
1803
+ it('handles badly nested qref', () => {
1804
+ const query = 'categories<{Category parent<{Category id="top"}>';
1805
+ const [options, ...selectors] = queryParser({
1806
+ query,
1807
+ entityClass: BlogPost,
1808
+ });
1809
+
1810
+ expect(options).toEqual({
1811
+ class: BlogPost,
1812
+ });
1813
+
1814
+ expect(selectors).toEqual([
1815
+ {
1816
+ type: '&',
1817
+ contain: [['categories', '{Category parent<{Category}']],
1818
+ equal: [['id', 'top']],
1819
+ },
1820
+ ]);
1821
+ });
1822
+
1823
+ it('parses a realistic query', () => {
1824
+ const query =
1825
+ 'labels<{Label [aggbox]}> -[trash] [delivered] -<spam> -<bare> -<autoreply> -<draft> -<sent> limit:10 reverse:true';
1826
+ const [options, ...selectors] = queryParser({
1827
+ query,
1828
+ entityClass: Mail,
1829
+ defaultFields: ['subject', 'from', 'sender'],
1830
+ qrefMap: {
1831
+ Mail: {
1832
+ class: Mail,
1833
+ defaultFields: ['subject', 'from', 'sender'],
1834
+ },
1835
+ Headers: {
1836
+ class: Headers,
1837
+ defaultFields: ['Message-ID'],
1838
+ },
1839
+ Body: {
1840
+ class: Body,
1841
+ defaultFields: ['text'],
1842
+ },
1843
+ Label: {
1844
+ class: Label,
1845
+ defaultFields: ['name', 'id'],
1846
+ },
1847
+ Domain: {
1848
+ class: Domain,
1849
+ defaultFields: ['fqdn'],
1850
+ },
1851
+ },
1852
+ bareHandler: (
1853
+ input,
1854
+ entityClass,
1855
+ defaultFields = ['subject', 'from', 'sender'],
1856
+ ) => {
1857
+ if (entityClass === Mail) {
1858
+ return {
1859
+ type: '|',
1860
+ search: defaultFields.map((field) => [field, input]) as [
1861
+ string,
1862
+ string,
1863
+ ][],
1864
+ qref: [
1865
+ [
1866
+ 'body',
1867
+ [
1868
+ { class: Body },
1869
+ {
1870
+ type: '&',
1871
+ search: ['text', input],
1872
+ },
1873
+ ],
1874
+ ],
1875
+ [
1876
+ 'body',
1877
+ [
1878
+ { class: Body },
1879
+ {
1880
+ type: '&',
1881
+ search: ['html', input],
1882
+ },
1883
+ ],
1884
+ ],
1885
+ [
1886
+ 'body',
1887
+ [
1888
+ { class: Body },
1889
+ {
1890
+ type: '&',
1891
+ search: ['attachments', input],
1892
+ },
1893
+ ],
1894
+ ],
1895
+ ],
1896
+ };
1897
+ }
1898
+ if (defaultFields.length) {
1899
+ return {
1900
+ type: '|',
1901
+ search: defaultFields.map((field) => [field, input]) as [
1902
+ string,
1903
+ string,
1904
+ ][],
1905
+ };
1906
+ }
1907
+ return {};
1908
+ },
1909
+ });
1910
+
1911
+ expect(options).toEqual({
1912
+ class: Mail,
1913
+ limit: 10,
1914
+ reverse: true,
1915
+ });
1916
+
1917
+ expect(selectors).toEqual([
1918
+ {
1919
+ type: '&',
1920
+ '!tag': ['spam', 'bare', 'autoreply', 'draft', 'sent'],
1921
+ '!truthy': ['trash'],
1922
+ qref: [
1923
+ [
1924
+ 'labels',
1925
+ [
1926
+ {
1927
+ class: Label,
1928
+ },
1929
+ {
1930
+ truthy: ['aggbox'],
1931
+ type: '&',
1932
+ },
1933
+ ],
1934
+ ],
1935
+ ],
1936
+ truthy: ['delivered'],
1937
+ },
1938
+ ]);
1939
+ });
1391
1940
  });