clarity-pattern-parser 11.3.9 → 11.3.11

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.
@@ -7,6 +7,19 @@ import { Regex } from "../patterns/Regex";
7
7
  import { Repeat } from "../patterns/Repeat";
8
8
  import { AutoComplete, AutoCompleteOptions } from "./AutoComplete";
9
9
  import { Optional } from "../patterns/Optional";
10
+ import { SuggestionOption } from "./SuggestionOption";
11
+
12
+ interface ExpectedOption {
13
+ text: string;
14
+ startIndex: number;
15
+ subElements: ExpectedSubElement[];
16
+ }
17
+
18
+ interface ExpectedSubElement {
19
+ text: string;
20
+ // maps to the name of the pattern
21
+ pattern: string;
22
+ }
10
23
 
11
24
  function generateFlagFromList(flagNames: string[]) {
12
25
  return flagNames.map(flagName => {
@@ -24,6 +37,26 @@ function generateFlagPattern(flagNames: string[]): Pattern {
24
37
  return flagPattern;
25
38
  }
26
39
 
40
+
41
+
42
+ function optionsMatchExpected(resultOptions: SuggestionOption[], expectedOptions: ExpectedOption[]) {
43
+
44
+ const expectedOptionsPatternNames = expectedOptions.map(e => e.subElements.map(s => s.pattern));
45
+ const resultOptionsPatternNames = resultOptions.map(r => r.suggestionSequence.map(s => s.pattern.name));
46
+
47
+
48
+ expect(resultOptions.length).toBe(expectedOptions.length);
49
+ resultOptions.forEach((resultOption, index) => {
50
+ expect(resultOption.text).toBe(expectedOptions[index].text);
51
+ expect(resultOption.startIndex).toBe(expectedOptions[index].startIndex);
52
+ expect(resultOption.suggestionSequence.length).toBe(expectedOptions[index].subElements.length);
53
+ expect(expectedOptionsPatternNames).toEqual(resultOptionsPatternNames);
54
+ });
55
+ }
56
+
57
+
58
+
59
+
27
60
  export function generateExpression(flagNames: string[]): Repeat {
28
61
  if (flagNames.length === 0) {
29
62
  // regex is purposefully impossible to satisfy
@@ -103,21 +136,35 @@ describe("AutoComplete", () => {
103
136
  const space = new Literal("space", " ");
104
137
  const doe = new Literal("doe", "Doe");
105
138
  const smith = new Literal("smith", "Smith");
106
- const name = new Sequence("name", [john, space, new Options("last-name", [smith, doe])]);
139
+ const lastNameOptions = new Options("last-name", [smith, doe]);
140
+ const name = new Sequence("name", [john, space, lastNameOptions]);
107
141
 
108
- const text = "John "
109
142
  const autoComplete = new AutoComplete(name);
143
+
144
+ const text = "John "
110
145
  const result = autoComplete.suggestFor(text);
111
- const expectedOptions = [{
146
+
147
+ const expectedOptions = [
148
+ {
112
149
  text: "Doe",
113
- startIndex: 5
114
- }, {
150
+ startIndex: 5,
151
+ subElements: [{
152
+ text: "Doe",
153
+ pattern: 'doe'
154
+ }]
155
+ },
156
+ {
115
157
  text: "Smith",
116
- startIndex: 5
117
- }];
158
+ startIndex: 5,
159
+ subElements: [{
160
+ text: "Smith",
161
+ pattern: smith.name
162
+ }]
163
+ }
164
+ ];
118
165
 
119
166
  expect(result.ast).toBeNull();
120
- expect(result.options).toEqual(expectedOptions);
167
+ optionsMatchExpected(result.options, expectedOptions);
121
168
  expect(result.errorAtIndex).toBe(text.length);
122
169
  expect(result.isComplete).toBeFalsy();
123
170
  expect(result.cursor).not.toBeNull();
@@ -129,18 +176,24 @@ describe("AutoComplete", () => {
129
176
  const space = new Literal("space", " ");
130
177
  const doe = new Literal("doe", "Doe");
131
178
  const smith = new Literal("smith", "Smith");
132
- const name = new Sequence("name", [john, space, new Options("last-name", [smith, doe])]);
179
+ const lastNameOptions = new Options("last-name", [smith, doe]);
180
+ const name = new Sequence("name", [john, space, lastNameOptions]);
133
181
 
134
182
  const text = "John Smi"
135
183
  const autoComplete = new AutoComplete(name);
136
184
  const result = autoComplete.suggestFor(text);
185
+
137
186
  const expectedOptions = [{
138
187
  text: "th",
139
- startIndex: 8
188
+ startIndex: 8,
189
+ subElements: [{
190
+ text: "Smith",
191
+ pattern: smith.name
192
+ }]
140
193
  }];
141
194
 
142
195
  expect(result.ast).toBeNull();
143
- expect(result.options).toEqual(expectedOptions);
196
+ optionsMatchExpected(result.options, expectedOptions);
144
197
  expect(result.errorAtIndex).toBe(text.length);
145
198
  expect(result.isComplete).toBeFalsy();
146
199
  expect(result.cursor).not.toBeNull();
@@ -161,12 +214,18 @@ describe("AutoComplete", () => {
161
214
  });
162
215
  const result = autoComplete.suggestFor("luke");
163
216
 
164
- const expected = [
165
- { text: " skywalker", startIndex: 4 },
166
- ];
167
-
217
+ const expectedOptions = [{
218
+ text: " skywalker",
219
+ startIndex: 4,
220
+ subElements: [{
221
+ text: " skywalker",
222
+ pattern: freeTextPattern.name
223
+ }]
224
+ }];
225
+
226
+
168
227
  expect(result.ast?.value).toBe("luke");
169
- expect(result.options).toEqual(expected);
228
+ optionsMatchExpected(result.options, expectedOptions);
170
229
  expect(result.errorAtIndex).toBeNull()
171
230
  expect(result.isComplete).toBeTruthy();
172
231
  expect(result.cursor).not.toBeNull();
@@ -192,11 +251,15 @@ describe("AutoComplete", () => {
192
251
  const result = autoComplete.suggestFor("jedi luke sky");
193
252
 
194
253
  const expected = [
195
- { text: "walker", startIndex: 13 },
254
+ { text: "walker", startIndex: 13, subElements: [{
255
+ text: "walker",
256
+ startIndex: 13,
257
+ pattern: freeTextPattern.name
258
+ }] },
196
259
  ];
197
260
 
198
261
  expect(result.ast?.value).toBe("jedi luke sky");
199
- expect(result.options).toEqual(expected);
262
+ optionsMatchExpected(result.options, expected);
200
263
  expect(result.errorAtIndex).toBeNull()
201
264
  expect(result.isComplete).toBeTruthy();
202
265
  expect(result.cursor).not.toBeNull();
@@ -215,15 +278,20 @@ describe("AutoComplete", () => {
215
278
  divider.setTokens([", "])
216
279
 
217
280
  const text = "John Doe";
218
- const autoComplete = new AutoComplete(new Repeat("last-names", name, { divider }));
281
+ const repeat = new Repeat("last-names", name, { divider });
282
+ const autoComplete = new AutoComplete(repeat);
219
283
  const result = autoComplete.suggestFor(text);
220
284
  const expectedOptions = [{
221
285
  text: ", ",
222
- startIndex: 8
286
+ startIndex: 8,
287
+ subElements: [{
288
+ text: ", ",
289
+ pattern: 'divider'
290
+ }]
223
291
  }];
224
292
 
225
293
  expect(result.ast?.value).toBe(text);
226
- expect(result.options).toEqual(expectedOptions);
294
+ optionsMatchExpected(result.options, expectedOptions);
227
295
  expect(result.errorAtIndex).toBeNull()
228
296
  expect(result.isComplete).toBeTruthy();
229
297
  expect(result.cursor).not.toBeNull();
@@ -236,11 +304,15 @@ describe("AutoComplete", () => {
236
304
  const result = autoComplete.suggestFor("Na");
237
305
  const expectedOptions = [{
238
306
  text: "me",
239
- startIndex: 2
307
+ startIndex: 2,
308
+ subElements: [{
309
+ text: "me",
310
+ pattern: 'name'
311
+ }]
240
312
  }];
241
313
 
242
314
  expect(result.ast).toBeNull();
243
- expect(result.options).toEqual(expectedOptions);
315
+ optionsMatchExpected(result.options, expectedOptions);
244
316
  expect(result.errorAtIndex).toBe(2);
245
317
  expect(result.isComplete).toBeFalsy();
246
318
  expect(result.cursor).not.toBeNull();
@@ -253,11 +325,15 @@ describe("AutoComplete", () => {
253
325
 
254
326
  const expectedOptions = [{
255
327
  text: "ame",
256
- startIndex: 1
328
+ startIndex: 1,
329
+ subElements: [{
330
+ text: "ame",
331
+ pattern: 'name'
332
+ }]
257
333
  }];
258
334
 
259
335
  expect(result.ast).toBeNull();
260
- expect(result.options).toEqual(expectedOptions);
336
+ optionsMatchExpected(result.options, expectedOptions);
261
337
  expect(result.errorAtIndex).toBe(1);
262
338
  expect(result.isComplete).toBeFalsy();
263
339
  expect(result.cursor).not.toBeNull();
@@ -270,7 +346,7 @@ describe("AutoComplete", () => {
270
346
  const result = autoComplete.suggestFor(text);
271
347
 
272
348
  expect(result.ast?.value).toBe(text);
273
- expect(result.options).toEqual([]);
349
+ optionsMatchExpected(result.options, []);
274
350
  expect(result.errorAtIndex).toBeNull();
275
351
  expect(result.isComplete).toBeTruthy();
276
352
  expect(result.cursor).not.toBeNull();
@@ -295,29 +371,41 @@ describe("AutoComplete", () => {
295
371
 
296
372
  const text = "Jack";
297
373
  const autoComplete = new AutoComplete(fullName, autoCompleteOptions);
298
- const { options, ast, errorAtIndex } = autoComplete.suggestFor(text);
374
+ const result = autoComplete.suggestFor(text);
299
375
 
300
- const expectedOptions = [
301
- { text: " Doe", startIndex: 4 },
302
- { text: " Smith", startIndex: 4 },
303
- { text: " Sparrow", startIndex: 4 },
376
+ const expectedOptions:ExpectedOption[] = [
377
+ { text: " Doe", startIndex: 4, subElements: [{
378
+ text: " ",
379
+ pattern: space.name
380
+ },{
381
+ text: "Doe",
382
+ pattern: doe.name
383
+ }] },
384
+ { text: " Smith", startIndex: 4, subElements: [{
385
+ text: " ",
386
+ pattern: space.name
387
+ },{
388
+ text: "Smith",
389
+ pattern: smith.name
390
+ }] },
391
+ { text: " Sparrow", startIndex: 4, subElements: [{
392
+ text: " ",
393
+ pattern: space.name
394
+ },{
395
+ text: "Sparrow",
396
+ pattern: "last-name"
397
+ }] },
304
398
  ];
305
399
 
306
- const results = expectedOptions.map(o => text.slice(0, o.startIndex) + o.text);
307
- const expectedResults = [
308
- "Jack Doe",
309
- "Jack Smith",
310
- "Jack Sparrow",
311
- ]
312
400
 
313
- expect(ast).toBeNull();
314
- expect(errorAtIndex).toBe(4);
315
- expect(options).toEqual(expectedOptions);
316
- expect(results).toEqual(expectedResults);
401
+ expect(result.ast).toBeNull();
402
+ expect(result.errorAtIndex).toBe(4);
403
+ optionsMatchExpected(result.options, expectedOptions);
317
404
 
318
405
  });
319
406
 
320
407
 
408
+
321
409
  test("Options AutoComplete on Root Pattern", () => {
322
410
 
323
411
  const jack = new Literal("first-name", "Jack");
@@ -327,25 +415,43 @@ describe("AutoComplete", () => {
327
415
  const divider = new Literal('divider', ', ');
328
416
  const repeat = new Repeat('name-list', names, { divider, trimDivider: true });
329
417
 
330
- const text = ''
331
-
332
418
  const autoCompleteOptions: AutoCompleteOptions = {
333
419
  customTokens: {
334
420
  'first-name': ["James"]
335
- }
421
+ },
422
+ disableDedupe: true
336
423
  };
337
424
  const autoComplete = new AutoComplete(repeat,autoCompleteOptions);
338
-
339
- const suggestion = autoComplete.suggestFor(text)
340
-
341
- const expectedOptions = [
342
- { text: "Jack", startIndex: 0 },
343
- { text: "John", startIndex: 0 },
344
- { text: "James", startIndex: 0 },
425
+
426
+ const text = ''
427
+ const results = autoComplete.suggestFor(text)
428
+
429
+
430
+ const expectedOptions:ExpectedOption[] = [
431
+
432
+ { text: "Jack", startIndex: 0, subElements: [{
433
+ text: "Jack",
434
+ pattern: jack.name
435
+ }] },
436
+ { text: "James", startIndex: 0, subElements: [{
437
+ text: "James",
438
+ pattern: 'first-name'
439
+ }] },
440
+ { text: "James", startIndex: 0, subElements: [{
441
+ text: "James",
442
+ pattern: 'first-name'
443
+ }] },
444
+ { text: "John", startIndex: 0, subElements: [{
445
+ text: "John",
446
+ pattern: john.name
447
+ }] },
345
448
  ];
346
449
 
347
- expect(suggestion.options).toEqual(expectedOptions)
348
-
450
+ optionsMatchExpected(results.options, expectedOptions);
451
+ // because autoCompleteOptions specifies "last-name" which is shared by two literals, we get two suggestions each mapping to a respective pattern of that name
452
+ expect(results.options[0].suggestionSequence[0].pattern.id).toBe(jack.id);
453
+ expect(results.options[2].suggestionSequence[0].pattern.id).toBe(john.id);
454
+
349
455
  })
350
456
 
351
457
  test("Options AutoComplete On Leaf Pattern", () => {
@@ -367,26 +473,41 @@ describe("AutoComplete", () => {
367
473
 
368
474
  const text = "Jack";
369
475
  const autoComplete = new AutoComplete(fullName, autoCompleteOptions);
370
- const { options, ast, errorAtIndex } = autoComplete.suggestFor(text);
371
- const expectedOptions = [
372
- { text: " Doe", startIndex: 4 },
373
- { text: " Smith", startIndex: 4 },
374
- { text: " Doe", startIndex: 4 },
375
- { text: " Smith", startIndex: 4 },
476
+ const results = autoComplete.suggestFor(text);
477
+ const expectedOptions:ExpectedOption[] = [
478
+ { text: " Doe", startIndex: 4, subElements: [{
479
+ text: " ",
480
+ pattern: space.name
481
+ },{
482
+ text: "Doe",
483
+ pattern: doe.name
484
+ }] },
485
+ { text: " Smith", startIndex: 4, subElements: [{
486
+ text: " ",
487
+ pattern: space.name
488
+ },{
489
+ text: "Smith",
490
+ pattern: smith.name
491
+ }] },
492
+ { text: " Doe", startIndex: 4, subElements: [{
493
+ text: " ",
494
+ pattern: space.name
495
+ },{
496
+ text: "Doe",
497
+ pattern: doe.name
498
+ }] },
499
+ { text: " Smith", startIndex: 4, subElements: [{
500
+ text: " ",
501
+ pattern: space.name
502
+ },{
503
+ text: "Smith",
504
+ pattern: smith.name
505
+ }] },
376
506
  ];
377
507
 
378
- const results = expectedOptions.map(o => text.slice(0, o.startIndex) + o.text);
379
- const expectedResults = [
380
- "Jack Doe",
381
- "Jack Smith",
382
- "Jack Doe",
383
- "Jack Smith",
384
- ]
385
-
386
- expect(ast).toBeNull();
387
- expect(errorAtIndex).toBe(4);
388
- expect(options).toEqual(expectedOptions);
389
- expect(results).toEqual(expectedResults)
508
+ expect(results.ast).toBeNull();
509
+ expect(results.errorAtIndex).toBe(4);
510
+ optionsMatchExpected(results.options, expectedOptions);
390
511
 
391
512
  });
392
513
 
@@ -401,96 +522,170 @@ describe("AutoComplete", () => {
401
522
  const both = new Options("both", [first, second]);
402
523
 
403
524
  const autoComplete = new AutoComplete(both);
404
- const result = autoComplete.suggestFor("John went to a gas station.");
405
- const expected = [
406
- { text: "the store.", startIndex: 12 },
407
- { text: "a bank.", startIndex: 12 }
525
+
526
+ const text = "John went to a gas station.";
527
+ const result = autoComplete.suggestFor(text);
528
+
529
+ const expected:ExpectedOption[] = [
530
+ { text: "the store.", startIndex: 12, subElements: [{
531
+ text: "the",
532
+ pattern: the.name
533
+ },] },
534
+ { text: "a bank.", startIndex: 12, subElements: [{
535
+ text: "a",
536
+ pattern: a.name
537
+ }] }
408
538
  ];
409
- expect(result.options).toEqual(expected);
539
+
540
+
541
+ optionsMatchExpected(result.options, expected);
410
542
  });
411
543
 
412
544
  test("Options on errors because of string ending, with match", () => {
545
+
546
+ const large = new Literal("large", "kahnnnnnn");
547
+ const medium = new Literal("medium", "kahnnnnn");
548
+ const small = new Literal("small", "kahn");
549
+
550
+
413
551
  const smalls = new Options("kahns", [
414
- new Literal("large", "kahnnnnnn"),
415
- new Literal("medium", "kahnnnnn"),
416
- new Literal("small", "kahn"),
552
+ large,
553
+ medium,
554
+ small,
417
555
  ]);
418
556
 
419
557
  const autoComplete = new AutoComplete(smalls);
420
558
  const result = autoComplete.suggestFor("kahn");
421
559
 
422
- const expected = [
423
- { text: "nnnnn", startIndex: 4 },
424
- { text: "nnnn", startIndex: 4 }
560
+ const expected:ExpectedOption[] = [
561
+ { text: "nnnnn", startIndex: 4, subElements: [{
562
+ text: "nnnnn",
563
+ pattern: large.name
564
+ }] },
565
+ { text: "nnnn", startIndex: 4, subElements: [{
566
+ text: "nnnn",
567
+ pattern: medium.name
568
+ }] }
425
569
  ];
426
570
 
427
- expect(result.options).toEqual(expected);
571
+ optionsMatchExpected(result.options, expected);
572
+
428
573
  expect(result.isComplete).toBeTruthy();
429
574
  });
430
575
 
576
+
431
577
  test("Options on errors because of string ending, between matches", () => {
578
+
579
+ const large = new Literal("large", "kahnnnnnn");
580
+ const medium = new Literal("medium", "kahnnnnn");
581
+ const small = new Literal("small", "kahn");
582
+
583
+
432
584
  const smalls = new Options("kahns", [
433
- new Literal("large", "kahnnnnnn"),
434
- new Literal("medium", "kahnnnnn"),
435
- new Literal("small", "kahn"),
585
+ large,
586
+ medium,
587
+ small,
436
588
  ]);
437
589
 
438
590
  const autoComplete = new AutoComplete(smalls);
439
591
  const result = autoComplete.suggestFor("kahnn");
440
592
 
441
- const expected = [
442
- { text: "nnnn", startIndex: 5 },
443
- { text: "nnn", startIndex: 5 }
593
+ const expected:ExpectedOption[] = [
594
+ { text: "nnnn", startIndex: 5, subElements: [{
595
+ text: "nnnn",
596
+ pattern: large.name
597
+ }] },
598
+ { text: "nnn", startIndex: 5, subElements: [{
599
+ text: "nnn",
600
+ pattern: medium.name
601
+ }] }
444
602
  ];
445
603
 
446
- expect(result.options).toEqual(expected);
604
+
605
+ optionsMatchExpected(result.options, expected);
447
606
  expect(result.isComplete).toBeFalsy();
448
607
  });
449
608
 
450
609
  test("Options on errors because of string ending, match middle", () => {
610
+ const large = new Literal("large", "kahnnnnnn");
611
+ const medium = new Literal("medium", "kahnnnnn");
612
+ const small = new Literal("small", "kahn");
451
613
  const smalls = new Options("kahns", [
452
- new Literal("large", "kahnnnnnn"),
453
- new Literal("medium", "kahnnnnn"),
454
- new Literal("small", "kahn"),
614
+ large,
615
+ medium,
616
+ small,
455
617
  ]);
456
618
 
457
619
  const autoComplete = new AutoComplete(smalls);
458
620
  const result = autoComplete.suggestFor("kahnnnnn");
459
621
 
460
- const expected = [
461
- { text: "n", startIndex: 8 },
622
+ const expected:ExpectedOption[] = [
623
+ { text: "n", startIndex: 8, subElements: [{
624
+ text: "n",
625
+ pattern: large.name
626
+ }] },
462
627
  ];
463
628
 
464
- expect(result.options).toEqual(expected);
629
+ optionsMatchExpected(result.options, expected);
630
+
465
631
  expect(result.isComplete).toBeTruthy();
466
632
  });
467
633
 
468
634
 
469
635
  test("Options on errors because of string ending on a variety, with match", () => {
636
+ const different3 = new Literal("different-3", "kahnnnnnnn3");
637
+ const different21 = new Literal("different-21", "kahnnnnnn21");
638
+ const different22 = new Literal("different-22", "kahnnnnnn22");
639
+ const different2 = new Literal("different-2", "kahnnnnnn2");
640
+ const different1 = new Literal("different", "kahnnnnnn1");
641
+ const large = new Literal("large", "kahnnnnnn");
642
+ const small = new Literal("small", "kahn");
643
+ const medium = new Literal("medium", "kahnnnnn");
644
+
470
645
  const smalls = new Options("kahns", [
471
- new Literal("different-3", "kahnnnnnnn3"),
472
- new Literal("different-21", "kahnnnnnn21"),
473
- new Literal("different-22", "kahnnnnnn22"),
474
- new Literal("different-2", "kahnnnnnn2"),
475
- new Literal("different", "kahnnnnnn1"),
476
- new Literal("large", "kahnnnnnn"),
477
- new Literal("medium", "kahnnnnn"),
478
- new Literal("small", "kahn"),
646
+ different3,
647
+ different21,
648
+ different22,
649
+ different2,
650
+ different1,
651
+ large,
652
+ medium,
653
+ small,
479
654
  ]);
480
655
 
481
656
  const autoComplete = new AutoComplete(smalls);
482
657
  const result = autoComplete.suggestFor("kahnnnnn");
483
658
 
484
- const expected = [
485
- { text: "nn3", startIndex: 8 },
486
- { text: "n21", startIndex: 8 },
487
- { text: "n22", startIndex: 8 },
488
- { text: "n2", startIndex: 8 },
489
- { text: "n1", startIndex: 8 },
490
- { text: "n", startIndex: 8 },
659
+ const expected:ExpectedOption[] = [
660
+ { text: "nn3", startIndex: 8, subElements: [{
661
+ text: "nn3",
662
+ pattern: different3.name
663
+ }] },
664
+ { text: "n21", startIndex: 8, subElements: [{
665
+ text: "n21",
666
+ pattern: different21.name
667
+ }] },
668
+ { text: "n22", startIndex: 8 , subElements: [{
669
+ text: "n22",
670
+ pattern: different22.name
671
+ }] },
672
+ { text: "n2", startIndex: 8, subElements: [{
673
+ text: "n2",
674
+ pattern: different2.name
675
+ }] },
676
+ { text: "n1", startIndex: 8, subElements: [{
677
+ text: "n1",
678
+ pattern: different1.name
679
+ }] },
680
+ { text: "n", startIndex: 8, subElements: [{
681
+ text: "n",
682
+ pattern: large.name
683
+ }] },
491
684
  ];
492
685
 
493
- expect(result.options).toEqual(expected);
686
+
687
+
688
+ optionsMatchExpected(result.options, expected);
494
689
  expect(result.isComplete).toBeTruthy();
495
690
  });
496
691
 
@@ -505,13 +700,23 @@ describe("AutoComplete", () => {
505
700
  const autoComplete = new AutoComplete(smalls);
506
701
  const result = autoComplete.suggestFor("kahn");
507
702
 
508
- const expected = [
509
- { text: "2", startIndex: 4 },
510
- { text: "1", startIndex: 4 },
511
- { text: "3", startIndex: 4 },
512
- ];
513
-
514
- expect(result.options).toEqual(expected);
703
+ const expected:ExpectedOption[] = [
704
+ { text: "2", startIndex: 4, subElements: [{
705
+ text: "2",
706
+ pattern: "two"
707
+ }] },
708
+ { text: "1", startIndex: 4, subElements: [{
709
+ text: "1",
710
+ pattern: "one"
711
+ }] },
712
+ { text: "3", startIndex: 4, subElements: [{
713
+ text: "3",
714
+ pattern: "three"
715
+ }] },
716
+ ];
717
+
718
+
719
+ optionsMatchExpected(result.options, expected);
515
720
  expect(result.isComplete).toBeTruthy();
516
721
  });
517
722
 
@@ -520,15 +725,57 @@ describe("AutoComplete", () => {
520
725
  const autoComplete = new AutoComplete(repeat);
521
726
  const result = autoComplete.suggestFor("a|a|");
522
727
 
523
- expect(result.options).toEqual([{ text: 'a', startIndex: 4 }]);
728
+ const expected:ExpectedOption[] = [
729
+ { text: "a", startIndex: 4, subElements: [{
730
+ text: "a",
731
+ pattern: "a"
732
+ }] },
733
+ ];
734
+
735
+
736
+ optionsMatchExpected(result.options, expected);
737
+
524
738
  });
525
739
 
740
+ test("Remove options divider", () => {
741
+ const jediLuke = new Literal(`jedi`, 'luke');
742
+ const names = new Options('names', [jediLuke]);
743
+ const literalA = new Literal('literal-a', 'a');
744
+ const literalB = new Literal('literal-b', 'b');
745
+
746
+ const optionsDivider = new Options('options-divider', [literalA, literalB]);
747
+
748
+ // control to prove the pattern works without trimDivider
749
+ const controlPattern = new Repeat('name-list', names, { divider: optionsDivider });
750
+ const controlAutoComplete = new AutoComplete(controlPattern);
751
+ const controlResult = controlAutoComplete.suggestFor('lukea');
752
+ expect(controlResult.isComplete).toEqual(true);
753
+
754
+ const trimPattern = new Repeat('name-list', names, { divider: optionsDivider, trimDivider: true });
755
+ const trimAutoComplete = new AutoComplete(trimPattern);
756
+
757
+ const trimResult = trimAutoComplete.suggestFor('lukea');
758
+ expect(trimResult.isComplete).toEqual(false);
759
+ })
760
+
761
+
762
+
763
+
526
764
  test("Expect Divider", () => {
527
765
  const repeat = new Repeat("repeat", new Literal("a", "a"), { divider: new Literal("pipe", "|") });
528
766
  const autoComplete = new AutoComplete(repeat);
529
767
  const result = autoComplete.suggestFor("a|a");
530
768
 
531
- expect(result.options).toEqual([{ text: '|', startIndex: 3 }]);
769
+
770
+ const expected:ExpectedOption[] = [
771
+ { text: '|', startIndex: 3, subElements: [{
772
+ text: '|',
773
+ pattern: 'pipe'
774
+ }] }
775
+ ];
776
+
777
+ optionsMatchExpected(result.options, expected);
778
+
532
779
  });
533
780
 
534
781
  test("Repeat with bad second repeat", () => {
@@ -536,7 +783,16 @@ describe("AutoComplete", () => {
536
783
  const autoComplete = new AutoComplete(repeat);
537
784
  const result = autoComplete.suggestFor("a|b");
538
785
 
539
- expect(result.options).toEqual([{ text: 'a', startIndex: 2 }]);
786
+
787
+ const expected:ExpectedOption[] = [
788
+ { text: "a", startIndex: 2, subElements: [{
789
+ text: "a",
790
+ pattern: "a"
791
+ }] },
792
+ ];
793
+
794
+ optionsMatchExpected(result.options, expected);
795
+
540
796
  });
541
797
 
542
798
  test("Repeat with bad trailing content", () => {
@@ -565,29 +821,48 @@ describe("AutoComplete", () => {
565
821
 
566
822
  const autoComplete = new AutoComplete(names);
567
823
  const results = autoComplete.suggestFor("John ");
568
- const expected = [
824
+ const expected:ExpectedOption[] = [
569
825
  {
570
826
  text: "Doe",
571
827
  startIndex: 5,
828
+ subElements: [{
829
+ text: "Doe",
830
+ pattern: "doe"
831
+ }]
572
832
  },
573
833
  {
574
834
  text: "Smith",
575
835
  startIndex: 5,
836
+ subElements: [{
837
+ text: "Smith",
838
+ pattern: "smith"
839
+ }]
576
840
  },
577
841
  {
578
842
  text: "Stockton",
579
843
  startIndex: 5,
844
+ subElements: [{
845
+ text: "Stockton",
846
+ pattern: "john-stockton"
847
+ }]
580
848
  },
581
849
  {
582
850
  text: "Johnson",
583
- startIndex: 5,
851
+ startIndex: 5,
852
+ subElements: [{
853
+ text: "Johnson",
854
+ pattern: "john-johnson"
855
+ }]
584
856
  },
585
857
  ];
586
- expect(results.options).toEqual(expected);
858
+
859
+
860
+ optionsMatchExpected(results.options, expected);
587
861
  expect(results.ast).toBeNull();
588
862
  expect(results.errorAtIndex).toBe(5);
589
863
  });
590
864
 
865
+
591
866
  test("Dedup suggestions", () => {
592
867
  const branchOne = new Sequence("branch-1", [new Literal("space", " "), new Literal("A", "A")]);
593
868
  const branchTwo = new Sequence("branch-2", [new Literal("space", " "), new Literal("B", "B")]);
@@ -595,11 +870,16 @@ describe("AutoComplete", () => {
595
870
 
596
871
  const autoComplete = new AutoComplete(eitherBranch);
597
872
  const results = autoComplete.suggestFor("");
598
- const expected = [{
873
+ const expected:ExpectedOption[] = [{
599
874
  startIndex: 0,
600
- text: " "
875
+ text: " ",
876
+ subElements: [{
877
+ text: " ",
878
+ pattern: "space"
879
+ }]
601
880
  }];
602
- expect(results.options).toEqual(expected);
881
+
882
+ optionsMatchExpected(results.options, expected);
603
883
  });
604
884
 
605
885
  test("Multiple Complex Branches", () => {
@@ -624,12 +904,23 @@ describe("AutoComplete", () => {
624
904
 
625
905
  const autoComplete = new AutoComplete(eitherBranch);
626
906
  const results = autoComplete.suggestFor(" B");
627
- const expected = [
628
- { startIndex: 3, text: "A" },
629
- { startIndex: 3, text: "B" },
630
- { startIndex: 3, text: "C" },
907
+
908
+ const expected:ExpectedOption[] = [
909
+ { startIndex: 3, text: "A", subElements: [{
910
+ text: "A",
911
+ pattern: "BA"
912
+ }] },
913
+ { startIndex: 3, text: "B", subElements: [{
914
+ text: "B",
915
+ pattern: "BB"
916
+ }] },
917
+ { startIndex: 3, text: "C", subElements: [{
918
+ text: "C",
919
+ pattern: "BC"
920
+ }] },
631
921
  ];
632
- expect(results.options).toEqual(expected);
922
+
923
+ optionsMatchExpected(results.options, expected);
633
924
  });
634
925
 
635
926
 
@@ -644,9 +935,15 @@ describe("AutoComplete", () => {
644
935
  const autoComplete = new AutoComplete(names);
645
936
  const suggestion = autoComplete.suggestFor("Jo");
646
937
 
647
- expect(suggestion.options).toEqual([
648
- { text: 'hn', startIndex: 2 }
649
- ]);
938
+ const expected:ExpectedOption[] = [
939
+ { text: 'hn', startIndex: 2, subElements: [{
940
+ text: 'hn',
941
+ pattern: "john"
942
+ }] },
943
+ ];
944
+
945
+ optionsMatchExpected(suggestion.options, expected);
946
+
650
947
  expect(suggestion.error?.lastIndex).toBe(2);
651
948
  });
652
949
 
@@ -679,13 +976,26 @@ describe("AutoComplete", () => {
679
976
  });
680
977
  const suggestion = autoComplete.suggestFor("John");
681
978
 
682
- expect(suggestion.options).toEqual([{
683
- "text": " Doe",
684
- "startIndex": 4
685
- }, {
686
- "text": " Smith",
687
- "startIndex": 4
688
- }]);
979
+
980
+ const expected:ExpectedOption[] = [
981
+ { text: " Doe", startIndex: 4, subElements: [{
982
+ text: " ",
983
+ pattern: "space"
984
+ },{
985
+ text: "Doe",
986
+ pattern: "doe"
987
+ }] },
988
+ { text: " Smith", startIndex: 4, subElements: [{
989
+ text: " ",
990
+ pattern: "space"
991
+ },{
992
+ text: "Smith",
993
+ pattern: "smith"
994
+ }] },
995
+ ];
996
+
997
+ optionsMatchExpected(suggestion.options, expected);
998
+
689
999
  });
690
1000
 
691
1001
  test("Repeat With Options", () => {
@@ -701,6 +1011,7 @@ describe("AutoComplete", () => {
701
1011
  const autoComplete = new AutoComplete(list);
702
1012
  const suggestion = autoComplete.suggestFor("John, ");
703
1013
 
1014
+
704
1015
  expect(suggestion).toBe(suggestion);
705
1016
  });
706
1017
 
@@ -724,6 +1035,7 @@ describe("AutoComplete", () => {
724
1035
  expect(suggestion).toBe(suggestion);
725
1036
  });
726
1037
 
1038
+
727
1039
  test("Mid sequence suggestion", () => {
728
1040
  const operator = new Options("operator", [
729
1041
  new Literal("==", "=="),
@@ -746,19 +1058,35 @@ describe("AutoComplete", () => {
746
1058
  );
747
1059
  const result = autoComplete.suggestFor("variable1 ==");
748
1060
 
749
- expect(result.options).toEqual([{
750
- text: " ",
751
- startIndex: 12,
752
- },
753
- {
754
- text: "variable3",
755
- startIndex: 12,
756
- },
757
- {
758
- text: "variable4",
759
- startIndex: 12,
760
- }
761
- ]);
1061
+
1062
+ const expected:ExpectedOption[] = [
1063
+ { text: " variable3", startIndex: 12, subElements: [{
1064
+ text: " ",
1065
+ pattern: "space"
1066
+ },{
1067
+ text: "variable3",
1068
+ pattern: "variable-3"
1069
+ }] },
1070
+ { text: " variable4", startIndex: 12, subElements: [{
1071
+ text: " ",
1072
+ pattern: "space"
1073
+ },{
1074
+ text: "variable4",
1075
+ pattern: "variable-4"
1076
+ }] },
1077
+ { text: "variable3", startIndex: 12, subElements: [{
1078
+ text: "variable3",
1079
+ pattern: "variable-3"
1080
+ }] },
1081
+ { text: "variable4", startIndex: 12, subElements: [{
1082
+ text: "variable4",
1083
+ pattern: "variable-4"
1084
+ }] },
1085
+
1086
+ ];
1087
+
1088
+ optionsMatchExpected(result.options, expected);
1089
+
762
1090
  });
763
1091
 
764
1092
 
@@ -787,6 +1115,151 @@ describe("AutoComplete", () => {
787
1115
  expect(customTokensMap).toEqual(copiedCustomTokensMap)
788
1116
  })
789
1117
 
1118
+
1119
+ test("Suggests entire greedy node, with appended successive nodes", () => {
1120
+
1121
+ const start = new Literal("start", "start");
1122
+ const separator = new Literal("separator", '==');
1123
+
1124
+ const aLiteral = new Literal("letter", "A");
1125
+ const bLiteral = new Literal("letter", "B");
1126
+ const cLiteral = new Literal("letter", "C");
1127
+ const abcOptions = new Options("letters-options", [aLiteral, bLiteral, cLiteral]);
1128
+
1129
+ const fullSequence = new Sequence("full-sequence", [start, separator, abcOptions]);
1130
+
1131
+ const autoCompleteOptions: AutoCompleteOptions = {
1132
+ greedyPatternNames: ["separator"],
1133
+ };
1134
+ const autoComplete = new AutoComplete(fullSequence, autoCompleteOptions);
1135
+
1136
+ const text = "start";
1137
+ const results = autoComplete.suggestFor(text);
1138
+
1139
+
1140
+ const expectedOptions:ExpectedOption[] = [
1141
+ { text: "==A", startIndex: 5, subElements: [{
1142
+ text: "==",
1143
+ pattern: separator.name
1144
+ },{
1145
+ text: "A",
1146
+ pattern: aLiteral.name
1147
+ }] },
1148
+ { text: "==B", startIndex: 5, subElements: [{
1149
+ text: "==",
1150
+ pattern: separator.name
1151
+ },{
1152
+ text: "B",
1153
+ pattern: bLiteral.name
1154
+ }] },
1155
+ { text: "==C", startIndex: 5, subElements: [{
1156
+ text: "==",
1157
+ pattern: separator.name
1158
+ },{
1159
+ text: "C",
1160
+ pattern: cLiteral.name
1161
+ }] },
1162
+ ];
1163
+
1164
+ optionsMatchExpected(results.options, expectedOptions);
1165
+
1166
+ });
1167
+
1168
+ test("incomplete greedy text, suggests node completion with appended successive nodes", () => {
1169
+
1170
+ const start = new Literal("start", "start");
1171
+ const separator = new Literal("separator", '==');
1172
+
1173
+ const aLiteral = new Literal("letter", "A");
1174
+ const bLiteral = new Literal("letter", "B");
1175
+ const cLiteral = new Literal("letter", "C");
1176
+ const abcOptions = new Options("letters-options", [aLiteral, bLiteral, cLiteral]);
1177
+
1178
+ const fullSequence = new Sequence("full-sequence", [start, separator, abcOptions]);
1179
+
1180
+ const autoCompleteOptions: AutoCompleteOptions = {
1181
+ greedyPatternNames: ["separator"],
1182
+ };
1183
+ const autoComplete = new AutoComplete(fullSequence, autoCompleteOptions);
1184
+
1185
+ const text = "start=";
1186
+ const results = autoComplete.suggestFor(text);
1187
+
1188
+ const expectedOptions:ExpectedOption[] = [
1189
+ { text: "=A", startIndex: 6, subElements: [{
1190
+ text: "=",
1191
+ pattern: separator.name
1192
+ },{
1193
+ text: "A",
1194
+ pattern: aLiteral.name
1195
+ }] },
1196
+ { text: "=B", startIndex: 6, subElements: [{
1197
+ text: "=",
1198
+ pattern: separator.name
1199
+ },{
1200
+ text: "B",
1201
+ pattern: bLiteral.name
1202
+ }] },
1203
+ { text: "=C", startIndex: 6, subElements: [{
1204
+ text: "=",
1205
+ pattern: separator.name
1206
+ },{
1207
+ text: "C",
1208
+ pattern: cLiteral.name
1209
+ }] },
1210
+ ];
1211
+
1212
+ optionsMatchExpected(results.options, expectedOptions);
1213
+
1214
+ });
1215
+
1216
+ test("greedy root node", () => {
1217
+ const separator = new Literal("separator", '==');
1218
+
1219
+ const aLiteral = new Literal("letter", "A");
1220
+ const bLiteral = new Literal("letter", "B");
1221
+ const cLiteral = new Literal("letter", "C");
1222
+ const abcOptions = new Options("letters-options", [aLiteral, bLiteral, cLiteral]);
1223
+
1224
+ const fullSequence = new Sequence("full-sequence", [separator, abcOptions]);
1225
+
1226
+ const autoCompleteOptions: AutoCompleteOptions = {
1227
+ greedyPatternNames: ["separator"],
1228
+ };
1229
+ const autoComplete = new AutoComplete(fullSequence, autoCompleteOptions);
1230
+
1231
+ const text = "";
1232
+ const results = autoComplete.suggestFor(text);
1233
+
1234
+ const expectedOptions:ExpectedOption[] = [
1235
+ { text: "==A", startIndex: 0, subElements: [{
1236
+ text: "==",
1237
+ pattern: separator.name
1238
+ },{
1239
+ text: "A",
1240
+ pattern: aLiteral.name
1241
+ }] },
1242
+ { text: "==B", startIndex: 0, subElements: [{
1243
+ text: "==",
1244
+ pattern: separator.name
1245
+ },{
1246
+ text: "B",
1247
+ pattern: bLiteral.name
1248
+ }] },
1249
+ { text: "==C", startIndex: 0, subElements: [{
1250
+ text: "==",
1251
+ pattern: separator.name
1252
+ },{
1253
+ text: "C",
1254
+ pattern: cLiteral.name
1255
+ }] },
1256
+ ];
1257
+
1258
+ optionsMatchExpected(results.options, expectedOptions);
1259
+ });
1260
+
1261
+
1262
+
790
1263
  });
791
1264
 
792
1265