@nymphjs/query-parser 1.0.0-beta.121 → 1.0.0-beta.123

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,7 @@ 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;
6
7
 
7
8
  describe('queryParser', () => {
8
9
  it('parses a basic query', () => {
@@ -55,13 +56,13 @@ describe('queryParser', () => {
55
56
  });
56
57
 
57
58
  it('gives the correct entity and default fields to bare queries', () => {
58
- const query = 'category<{cat search}>';
59
+ const query = 'category<{Cat search}>';
59
60
  const [options, ...selectors] = queryParser({
60
61
  query,
61
62
  entityClass: BlogPost,
62
63
  defaultFields: ['text'],
63
64
  qrefMap: {
64
- cat: {
65
+ Cat: {
65
66
  class: Category,
66
67
  defaultFields: ['name'],
67
68
  },
@@ -97,13 +98,13 @@ describe('queryParser', () => {
97
98
 
98
99
  it('parses the example query from the readme', () => {
99
100
  const query =
100
- 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{cat Tech}>';
101
+ 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{Cat Tech}>';
101
102
  const [options, ...selectors] = queryParser({
102
103
  query,
103
104
  entityClass: BlogPost,
104
105
  defaultFields: ['title', 'body'],
105
106
  qrefMap: {
106
- cat: {
107
+ Cat: {
107
108
  class: Category,
108
109
  defaultFields: ['name'],
109
110
  },
@@ -289,6 +290,108 @@ describe('queryParser', () => {
289
290
  ]);
290
291
  });
291
292
 
293
+ it('parses a query with not search', () => {
294
+ const query = 'limit:4 foobar -prop(a search string)';
295
+ const [options, ...selectors] = queryParser({
296
+ query,
297
+ entityClass: BlogPost,
298
+ defaultFields: ['title', 'body'],
299
+ qrefMap: {
300
+ cat: {
301
+ class: Category,
302
+ defaultFields: ['name'],
303
+ },
304
+ },
305
+ });
306
+
307
+ expect(options).toEqual({
308
+ class: BlogPost,
309
+ limit: 4,
310
+ });
311
+
312
+ expect(selectors).toEqual([
313
+ {
314
+ type: '&',
315
+ '!search': [['prop', 'a search string']],
316
+ },
317
+ {
318
+ type: '|',
319
+ ilike: [
320
+ ['title', '%foobar%'],
321
+ ['body', '%foobar%'],
322
+ ],
323
+ },
324
+ ]);
325
+ });
326
+
327
+ it('parses a query with colon search', () => {
328
+ const query = 'limit:4 foobar prop:string';
329
+ const [options, ...selectors] = queryParser({
330
+ query,
331
+ entityClass: BlogPost,
332
+ defaultFields: ['title', 'body'],
333
+ qrefMap: {
334
+ cat: {
335
+ class: Category,
336
+ defaultFields: ['name'],
337
+ },
338
+ },
339
+ });
340
+
341
+ expect(options).toEqual({
342
+ class: BlogPost,
343
+ limit: 4,
344
+ });
345
+
346
+ expect(selectors).toEqual([
347
+ {
348
+ type: '&',
349
+ search: [['prop', 'string']],
350
+ },
351
+ {
352
+ type: '|',
353
+ ilike: [
354
+ ['title', '%foobar%'],
355
+ ['body', '%foobar%'],
356
+ ],
357
+ },
358
+ ]);
359
+ });
360
+
361
+ it('parses a query with not colon search', () => {
362
+ const query = 'limit:4 foobar -prop:string';
363
+ const [options, ...selectors] = queryParser({
364
+ query,
365
+ entityClass: BlogPost,
366
+ defaultFields: ['title', 'body'],
367
+ qrefMap: {
368
+ cat: {
369
+ class: Category,
370
+ defaultFields: ['name'],
371
+ },
372
+ },
373
+ });
374
+
375
+ expect(options).toEqual({
376
+ class: BlogPost,
377
+ limit: 4,
378
+ });
379
+
380
+ expect(selectors).toEqual([
381
+ {
382
+ type: '&',
383
+ '!search': [['prop', 'string']],
384
+ },
385
+ {
386
+ type: '|',
387
+ ilike: [
388
+ ['title', '%foobar%'],
389
+ ['body', '%foobar%'],
390
+ ],
391
+ },
392
+ ]);
393
+ });
394
+
292
395
  it('parses a query with search in a selector', () => {
293
396
  const query =
294
397
  'limit:4 foobar (| prop(a search string) prop2("another search string"))';
@@ -332,6 +435,53 @@ describe('queryParser', () => {
332
435
  ]);
333
436
  });
334
437
 
438
+ it('parses a query with colon search in a selector', () => {
439
+ const query =
440
+ 'limit:4 foobar (| prop:string prop2:"another search string" -prop3:pickle -prop4:\'another -pickle string\')';
441
+ const [options, ...selectors] = queryParser({
442
+ query,
443
+ entityClass: BlogPost,
444
+ defaultFields: ['title', 'body'],
445
+ qrefMap: {
446
+ cat: {
447
+ class: Category,
448
+ defaultFields: ['name'],
449
+ },
450
+ },
451
+ });
452
+
453
+ expect(options).toEqual({
454
+ class: BlogPost,
455
+ limit: 4,
456
+ });
457
+
458
+ expect(selectors).toEqual([
459
+ {
460
+ type: '&',
461
+ selector: [
462
+ {
463
+ type: '|',
464
+ search: [
465
+ ['prop', 'string'],
466
+ ['prop2', '"another search string"'],
467
+ ],
468
+ '!search': [
469
+ ['prop3', 'pickle'],
470
+ ['prop4', "'another -pickle string'"],
471
+ ],
472
+ },
473
+ ],
474
+ },
475
+ {
476
+ type: '|',
477
+ ilike: [
478
+ ['title', '%foobar%'],
479
+ ['body', '%foobar%'],
480
+ ],
481
+ },
482
+ ]);
483
+ });
484
+
335
485
  it('parses a query with lt and tag', () => {
336
486
  const query = 'limit:4 foobar mdate<"2 weeks ago" <tag>';
337
487
  const [options, ...selectors] = queryParser({
@@ -537,7 +687,7 @@ describe('queryParser', () => {
537
687
  });
538
688
 
539
689
  it('parses simple not equal clauses', () => {
540
- const query = 'prop!=string';
690
+ const query = '-prop=string';
541
691
  const [options, ...selectors] = queryParser({
542
692
  query,
543
693
  entityClass: BlogPost,
@@ -575,7 +725,7 @@ describe('queryParser', () => {
575
725
  });
576
726
 
577
727
  it('parses quoted not equal clauses', () => {
578
- const query = 'prop!="a string"';
728
+ const query = '-prop="a string"';
579
729
  const [options, ...selectors] = queryParser({
580
730
  query,
581
731
  entityClass: BlogPost,
@@ -613,7 +763,7 @@ describe('queryParser', () => {
613
763
  });
614
764
 
615
765
  it('parses json not equal clauses', () => {
616
- const query = 'prop!=true';
766
+ const query = '-prop=true';
617
767
  const [options, ...selectors] = queryParser({
618
768
  query,
619
769
  entityClass: BlogPost,
@@ -651,7 +801,7 @@ describe('queryParser', () => {
651
801
  });
652
802
 
653
803
  it('parses not guid clauses', () => {
654
- const query = '{!111111111111111111111111}';
804
+ const query = '-{111111111111111111111111}';
655
805
  const [options, ...selectors] = queryParser({
656
806
  query,
657
807
  entityClass: BlogPost,
@@ -689,7 +839,7 @@ describe('queryParser', () => {
689
839
  });
690
840
 
691
841
  it('parses not tag clauses', () => {
692
- const query = '<!tagname>';
842
+ const query = '-<tagname>';
693
843
  const [options, ...selectors] = queryParser({
694
844
  query,
695
845
  entityClass: BlogPost,
@@ -727,7 +877,7 @@ describe('queryParser', () => {
727
877
  });
728
878
 
729
879
  it('parses not truthy clauses', () => {
730
- const query = '[!truthyname]';
880
+ const query = '-[truthyname]';
731
881
  const [options, ...selectors] = queryParser({
732
882
  query,
733
883
  entityClass: BlogPost,
@@ -765,7 +915,7 @@ describe('queryParser', () => {
765
915
  });
766
916
 
767
917
  it('parses not ref clauses', () => {
768
- const query = 'prop!<{111111111111111111111111}>';
918
+ const query = '-prop<{111111111111111111111111}>';
769
919
  const [options, ...selectors] = queryParser({
770
920
  query,
771
921
  entityClass: BlogPost,
@@ -803,7 +953,7 @@ describe('queryParser', () => {
803
953
  });
804
954
 
805
955
  it('parses simple not contain clauses', () => {
806
- const query = 'prop!<string>';
956
+ const query = '-prop<string>';
807
957
  const [options, ...selectors] = queryParser({
808
958
  query,
809
959
  entityClass: BlogPost,
@@ -841,7 +991,7 @@ describe('queryParser', () => {
841
991
  });
842
992
 
843
993
  it('parses json not contain clauses', () => {
844
- const query = 'prop!<true>';
994
+ const query = '-prop<true>';
845
995
  const [options, ...selectors] = queryParser({
846
996
  query,
847
997
  entityClass: BlogPost,
@@ -879,7 +1029,7 @@ describe('queryParser', () => {
879
1029
  });
880
1030
 
881
1031
  it('parses not match clauses', () => {
882
- const query = 'prop!~/regex/';
1032
+ const query = '-prop~/regex/';
883
1033
  const [options, ...selectors] = queryParser({
884
1034
  query,
885
1035
  entityClass: BlogPost,
@@ -917,7 +1067,7 @@ describe('queryParser', () => {
917
1067
  });
918
1068
 
919
1069
  it('parses not imatch clauses', () => {
920
- const query = 'prop!~/regex/i';
1070
+ const query = '-prop~/regex/i';
921
1071
  const [options, ...selectors] = queryParser({
922
1072
  query,
923
1073
  entityClass: BlogPost,
@@ -955,7 +1105,7 @@ describe('queryParser', () => {
955
1105
  });
956
1106
 
957
1107
  it('parses simple not like clauses', () => {
958
- const query = 'prop!~pattern';
1108
+ const query = '-prop~pattern';
959
1109
  const [options, ...selectors] = queryParser({
960
1110
  query,
961
1111
  entityClass: BlogPost,
@@ -993,7 +1143,7 @@ describe('queryParser', () => {
993
1143
  });
994
1144
 
995
1145
  it('parses quoted not like clauses', () => {
996
- const query = 'prop!~"a pattern"';
1146
+ const query = '-prop~"a pattern"';
997
1147
  const [options, ...selectors] = queryParser({
998
1148
  query,
999
1149
  entityClass: BlogPost,
@@ -1031,7 +1181,7 @@ describe('queryParser', () => {
1031
1181
  });
1032
1182
 
1033
1183
  it('parses not ilike clauses', () => {
1034
- const query = 'prop!~"a pattern"i';
1184
+ const query = '-prop~"a pattern"i';
1035
1185
  const [options, ...selectors] = queryParser({
1036
1186
  query,
1037
1187
  entityClass: BlogPost,
@@ -1068,6 +1218,25 @@ describe('queryParser', () => {
1068
1218
  ]);
1069
1219
  });
1070
1220
 
1221
+ it('parses simple not gt clauses', () => {
1222
+ const query = '-prop>30.5';
1223
+ const [options, ...selectors] = queryParser({
1224
+ query,
1225
+ entityClass: BlogPost,
1226
+ });
1227
+
1228
+ expect(options).toEqual({
1229
+ class: BlogPost,
1230
+ });
1231
+
1232
+ expect(selectors).toEqual([
1233
+ {
1234
+ type: '&',
1235
+ '!gt': [['prop', 30.5]],
1236
+ },
1237
+ ]);
1238
+ });
1239
+
1071
1240
  it('parses simple relative gt clauses', () => {
1072
1241
  const query = 'prop>yesterday';
1073
1242
  const [options, ...selectors] = queryParser({
@@ -1087,6 +1256,25 @@ describe('queryParser', () => {
1087
1256
  ]);
1088
1257
  });
1089
1258
 
1259
+ it('parses simple relative not gt clauses', () => {
1260
+ const query = '-prop>yesterday';
1261
+ const [options, ...selectors] = queryParser({
1262
+ query,
1263
+ entityClass: BlogPost,
1264
+ });
1265
+
1266
+ expect(options).toEqual({
1267
+ class: BlogPost,
1268
+ });
1269
+
1270
+ expect(selectors).toEqual([
1271
+ {
1272
+ type: '&',
1273
+ '!gt': [['prop', null, 'yesterday']],
1274
+ },
1275
+ ]);
1276
+ });
1277
+
1090
1278
  it('parses quote relative gt clauses', () => {
1091
1279
  const query = 'prop>"3 weeks ago"';
1092
1280
  const [options, ...selectors] = queryParser({
@@ -1106,6 +1294,25 @@ describe('queryParser', () => {
1106
1294
  ]);
1107
1295
  });
1108
1296
 
1297
+ it('parses quote relative not gt clauses', () => {
1298
+ const query = '-prop>"3 weeks ago"';
1299
+ const [options, ...selectors] = queryParser({
1300
+ query,
1301
+ entityClass: BlogPost,
1302
+ });
1303
+
1304
+ expect(options).toEqual({
1305
+ class: BlogPost,
1306
+ });
1307
+
1308
+ expect(selectors).toEqual([
1309
+ {
1310
+ type: '&',
1311
+ '!gt': [['prop', null, '3 weeks ago']],
1312
+ },
1313
+ ]);
1314
+ });
1315
+
1109
1316
  it('parses simple gte clauses', () => {
1110
1317
  const query = 'prop>=30.5';
1111
1318
  const [options, ...selectors] = queryParser({
@@ -1125,6 +1332,25 @@ describe('queryParser', () => {
1125
1332
  ]);
1126
1333
  });
1127
1334
 
1335
+ it('parses simple not gte clauses', () => {
1336
+ const query = '-prop>=30.5';
1337
+ const [options, ...selectors] = queryParser({
1338
+ query,
1339
+ entityClass: BlogPost,
1340
+ });
1341
+
1342
+ expect(options).toEqual({
1343
+ class: BlogPost,
1344
+ });
1345
+
1346
+ expect(selectors).toEqual([
1347
+ {
1348
+ type: '&',
1349
+ '!gte': [['prop', 30.5]],
1350
+ },
1351
+ ]);
1352
+ });
1353
+
1128
1354
  it('parses simple relative gte clauses', () => {
1129
1355
  const query = 'prop>=yesterday';
1130
1356
  const [options, ...selectors] = queryParser({
@@ -1144,6 +1370,25 @@ describe('queryParser', () => {
1144
1370
  ]);
1145
1371
  });
1146
1372
 
1373
+ it('parses simple relative not gte clauses', () => {
1374
+ const query = '-prop>=yesterday';
1375
+ const [options, ...selectors] = queryParser({
1376
+ query,
1377
+ entityClass: BlogPost,
1378
+ });
1379
+
1380
+ expect(options).toEqual({
1381
+ class: BlogPost,
1382
+ });
1383
+
1384
+ expect(selectors).toEqual([
1385
+ {
1386
+ type: '&',
1387
+ '!gte': [['prop', null, 'yesterday']],
1388
+ },
1389
+ ]);
1390
+ });
1391
+
1147
1392
  it('parses quote relative gte clauses', () => {
1148
1393
  const query = 'prop>="3 weeks ago"';
1149
1394
  const [options, ...selectors] = queryParser({
@@ -1163,6 +1408,25 @@ describe('queryParser', () => {
1163
1408
  ]);
1164
1409
  });
1165
1410
 
1411
+ it('parses quote relative not gte clauses', () => {
1412
+ const query = '-prop>="3 weeks ago"';
1413
+ const [options, ...selectors] = queryParser({
1414
+ query,
1415
+ entityClass: BlogPost,
1416
+ });
1417
+
1418
+ expect(options).toEqual({
1419
+ class: BlogPost,
1420
+ });
1421
+
1422
+ expect(selectors).toEqual([
1423
+ {
1424
+ type: '&',
1425
+ '!gte': [['prop', null, '3 weeks ago']],
1426
+ },
1427
+ ]);
1428
+ });
1429
+
1166
1430
  it('parses simple lt clauses', () => {
1167
1431
  const query = 'prop<30.5';
1168
1432
  const [options, ...selectors] = queryParser({
@@ -1182,6 +1446,25 @@ describe('queryParser', () => {
1182
1446
  ]);
1183
1447
  });
1184
1448
 
1449
+ it('parses simple not lt clauses', () => {
1450
+ const query = '-prop<30.5';
1451
+ const [options, ...selectors] = queryParser({
1452
+ query,
1453
+ entityClass: BlogPost,
1454
+ });
1455
+
1456
+ expect(options).toEqual({
1457
+ class: BlogPost,
1458
+ });
1459
+
1460
+ expect(selectors).toEqual([
1461
+ {
1462
+ type: '&',
1463
+ '!lt': [['prop', 30.5]],
1464
+ },
1465
+ ]);
1466
+ });
1467
+
1185
1468
  it('parses simple relative lt clauses', () => {
1186
1469
  const query = 'prop<yesterday';
1187
1470
  const [options, ...selectors] = queryParser({
@@ -1201,6 +1484,25 @@ describe('queryParser', () => {
1201
1484
  ]);
1202
1485
  });
1203
1486
 
1487
+ it('parses simple relative not lt clauses', () => {
1488
+ const query = '-prop<yesterday';
1489
+ const [options, ...selectors] = queryParser({
1490
+ query,
1491
+ entityClass: BlogPost,
1492
+ });
1493
+
1494
+ expect(options).toEqual({
1495
+ class: BlogPost,
1496
+ });
1497
+
1498
+ expect(selectors).toEqual([
1499
+ {
1500
+ type: '&',
1501
+ '!lt': [['prop', null, 'yesterday']],
1502
+ },
1503
+ ]);
1504
+ });
1505
+
1204
1506
  it('parses quote relative lt clauses', () => {
1205
1507
  const query = 'prop<"3 weeks ago"';
1206
1508
  const [options, ...selectors] = queryParser({
@@ -1220,6 +1522,25 @@ describe('queryParser', () => {
1220
1522
  ]);
1221
1523
  });
1222
1524
 
1525
+ it('parses quote relative not lt clauses', () => {
1526
+ const query = '-prop<"3 weeks ago"';
1527
+ const [options, ...selectors] = queryParser({
1528
+ query,
1529
+ entityClass: BlogPost,
1530
+ });
1531
+
1532
+ expect(options).toEqual({
1533
+ class: BlogPost,
1534
+ });
1535
+
1536
+ expect(selectors).toEqual([
1537
+ {
1538
+ type: '&',
1539
+ '!lt': [['prop', null, '3 weeks ago']],
1540
+ },
1541
+ ]);
1542
+ });
1543
+
1223
1544
  it('parses simple lte clauses', () => {
1224
1545
  const query = 'prop<=30.5';
1225
1546
  const [options, ...selectors] = queryParser({
@@ -1239,6 +1560,25 @@ describe('queryParser', () => {
1239
1560
  ]);
1240
1561
  });
1241
1562
 
1563
+ it('parses simple not lte clauses', () => {
1564
+ const query = '-prop<=30.5';
1565
+ const [options, ...selectors] = queryParser({
1566
+ query,
1567
+ entityClass: BlogPost,
1568
+ });
1569
+
1570
+ expect(options).toEqual({
1571
+ class: BlogPost,
1572
+ });
1573
+
1574
+ expect(selectors).toEqual([
1575
+ {
1576
+ type: '&',
1577
+ '!lte': [['prop', 30.5]],
1578
+ },
1579
+ ]);
1580
+ });
1581
+
1242
1582
  it('parses simple relative lte clauses', () => {
1243
1583
  const query = 'prop<=yesterday';
1244
1584
  const [options, ...selectors] = queryParser({
@@ -1258,6 +1598,25 @@ describe('queryParser', () => {
1258
1598
  ]);
1259
1599
  });
1260
1600
 
1601
+ it('parses simple relative not lte clauses', () => {
1602
+ const query = '-prop<=yesterday';
1603
+ const [options, ...selectors] = queryParser({
1604
+ query,
1605
+ entityClass: BlogPost,
1606
+ });
1607
+
1608
+ expect(options).toEqual({
1609
+ class: BlogPost,
1610
+ });
1611
+
1612
+ expect(selectors).toEqual([
1613
+ {
1614
+ type: '&',
1615
+ '!lte': [['prop', null, 'yesterday']],
1616
+ },
1617
+ ]);
1618
+ });
1619
+
1261
1620
  it('parses quote relative lte clauses', () => {
1262
1621
  const query = 'prop<="3 weeks ago"';
1263
1622
  const [options, ...selectors] = queryParser({
@@ -1277,9 +1636,28 @@ describe('queryParser', () => {
1277
1636
  ]);
1278
1637
  });
1279
1638
 
1639
+ it('parses quote relative not lte clauses', () => {
1640
+ const query = '-prop<="3 weeks ago"';
1641
+ const [options, ...selectors] = queryParser({
1642
+ query,
1643
+ entityClass: BlogPost,
1644
+ });
1645
+
1646
+ expect(options).toEqual({
1647
+ class: BlogPost,
1648
+ });
1649
+
1650
+ expect(selectors).toEqual([
1651
+ {
1652
+ type: '&',
1653
+ '!lte': [['prop', null, '3 weeks ago']],
1654
+ },
1655
+ ]);
1656
+ });
1657
+
1280
1658
  it('parses complex selectors', () => {
1281
1659
  const query =
1282
- '(! [published] <draft>) (| cdate>"6 months ago" mdate>"1 month ago")';
1660
+ '(! [published] <draft>) (| -cdate>"6 months ago" mdate>"1 month ago")';
1283
1661
  const [options, ...selectors] = queryParser({
1284
1662
  query,
1285
1663
  entityClass: BlogPost,
@@ -1295,10 +1673,8 @@ describe('queryParser', () => {
1295
1673
  selector: [
1296
1674
  {
1297
1675
  type: '|',
1298
- gt: [
1299
- ['cdate', null, '6 months ago'],
1300
- ['mdate', null, '1 month ago'],
1301
- ],
1676
+ '!gt': [['cdate', null, '6 months ago']],
1677
+ gt: [['mdate', null, '1 month ago']],
1302
1678
  },
1303
1679
  {
1304
1680
  type: '!&',
@@ -1312,7 +1688,7 @@ describe('queryParser', () => {
1312
1688
 
1313
1689
  it('parses nested selectors', () => {
1314
1690
  const query =
1315
- '(! [published] <draft> (| cdate>"6 months ago" mdate>"1 month ago"))';
1691
+ '(! [published] <draft> (| -cdate>"6 months ago" mdate>"1 month ago"))';
1316
1692
  const [options, ...selectors] = queryParser({
1317
1693
  query,
1318
1694
  entityClass: BlogPost,
@@ -1330,10 +1706,8 @@ describe('queryParser', () => {
1330
1706
  selector: [
1331
1707
  {
1332
1708
  type: '|',
1333
- gt: [
1334
- ['cdate', null, '6 months ago'],
1335
- ['mdate', null, '1 month ago'],
1336
- ],
1709
+ '!gt': [['cdate', null, '6 months ago']],
1710
+ gt: [['mdate', null, '1 month ago']],
1337
1711
  },
1338
1712
  ],
1339
1713
  },
@@ -1342,7 +1716,7 @@ describe('queryParser', () => {
1342
1716
 
1343
1717
  it('parses nested qref clauses', () => {
1344
1718
  const query =
1345
- 'categories<{Category parent<{Category id="sort+newsletters"}>}> ';
1719
+ 'categories<{Category parent<{Category list<{List frosting name(favorite)}>}> -parent<{Category id="sort+newsletters"}>}>';
1346
1720
  const [options, ...selectors] = queryParser({
1347
1721
  query,
1348
1722
  entityClass: BlogPost,
@@ -1352,6 +1726,10 @@ describe('queryParser', () => {
1352
1726
  class: Category,
1353
1727
  defaultFields: ['name'],
1354
1728
  },
1729
+ List: {
1730
+ class: List,
1731
+ defaultFields: ['name'],
1732
+ },
1355
1733
  },
1356
1734
  });
1357
1735
 
@@ -1370,6 +1748,33 @@ describe('queryParser', () => {
1370
1748
  {
1371
1749
  type: '&',
1372
1750
  qref: [
1751
+ [
1752
+ 'parent',
1753
+ [
1754
+ { class: Category },
1755
+ {
1756
+ type: '&',
1757
+ qref: [
1758
+ [
1759
+ 'list',
1760
+ [
1761
+ { class: List },
1762
+ {
1763
+ type: '&',
1764
+ search: [['name', 'favorite']],
1765
+ },
1766
+ {
1767
+ type: '|',
1768
+ ilike: [['name', '%frosting%']],
1769
+ },
1770
+ ],
1771
+ ],
1772
+ ],
1773
+ },
1774
+ ],
1775
+ ],
1776
+ ],
1777
+ '!qref': [
1373
1778
  [
1374
1779
  'parent',
1375
1780
  [
@@ -1388,4 +1793,24 @@ describe('queryParser', () => {
1388
1793
  },
1389
1794
  ]);
1390
1795
  });
1796
+
1797
+ it('handles badly nested qref', () => {
1798
+ const query = 'categories<{Category parent<{Category id="top"}>';
1799
+ const [options, ...selectors] = queryParser({
1800
+ query,
1801
+ entityClass: BlogPost,
1802
+ });
1803
+
1804
+ expect(options).toEqual({
1805
+ class: BlogPost,
1806
+ });
1807
+
1808
+ expect(selectors).toEqual([
1809
+ {
1810
+ type: '&',
1811
+ contain: [['categories', '{Category parent<{Category}']],
1812
+ equal: [['id', 'top']],
1813
+ },
1814
+ ]);
1815
+ });
1391
1816
  });