@player-tools/json-language-service 0.13.0-next.3 → 0.13.0-next.5

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,1923 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import { CommonTypes, Types } from "@player-tools/static-xlrs";
3
+ import { PlayerLanguageService } from "../..";
4
+ import { toTextDocument } from "../../utils";
5
+
6
+ describe("SchemaValidationPlugin", () => {
7
+ let service: PlayerLanguageService;
8
+
9
+ beforeEach(async () => {
10
+ service = new PlayerLanguageService();
11
+ await service.setAssetTypesFromModule([Types, CommonTypes]);
12
+ });
13
+
14
+ describe("schema structure validation", () => {
15
+ test("reports error when schema is missing ROOT", async () => {
16
+ const document = toTextDocument(
17
+ JSON.stringify(
18
+ {
19
+ id: "foo",
20
+ views: [],
21
+ navigation: { BEGIN: "FLOW1" },
22
+ schema: {
23
+ SomeType: {
24
+ foo: { type: "SomeType" },
25
+ },
26
+ },
27
+ },
28
+ null,
29
+ 2,
30
+ ),
31
+ );
32
+ const diagnostics = await service.validateTextDocument(document);
33
+ expect(diagnostics).toMatchInlineSnapshot(`
34
+ [
35
+ {
36
+ "message": "Content Validation Error - missing: Property "ROOT" missing from type "Schema.Schema"",
37
+ "range": {
38
+ "end": {
39
+ "character": 3,
40
+ "line": 12,
41
+ },
42
+ "start": {
43
+ "character": 12,
44
+ "line": 6,
45
+ },
46
+ },
47
+ "severity": 1,
48
+ },
49
+ {
50
+ "message": "Schema Validation Error: Schema must have a "ROOT" key.",
51
+ "range": {
52
+ "end": {
53
+ "character": 3,
54
+ "line": 12,
55
+ },
56
+ "start": {
57
+ "character": 12,
58
+ "line": 6,
59
+ },
60
+ },
61
+ "severity": 1,
62
+ },
63
+ ]
64
+ `);
65
+ });
66
+
67
+ test("reports error when DataType is missing type property", async () => {
68
+ const document = toTextDocument(
69
+ JSON.stringify(
70
+ {
71
+ id: "foo",
72
+ views: [],
73
+ navigation: { BEGIN: "FLOW1" },
74
+ schema: {
75
+ ROOT: {
76
+ application: {
77
+ validation: [],
78
+ },
79
+ },
80
+ },
81
+ },
82
+ null,
83
+ 2,
84
+ ),
85
+ );
86
+ const diagnostics = await service.validateTextDocument(document);
87
+ expect(diagnostics).toMatchInlineSnapshot(`
88
+ [
89
+ {
90
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
91
+ "range": {
92
+ "end": {
93
+ "character": 7,
94
+ "line": 10,
95
+ },
96
+ "start": {
97
+ "character": 21,
98
+ "line": 8,
99
+ },
100
+ },
101
+ "severity": 1,
102
+ },
103
+ {
104
+ "message": "Schema Validation Error: Schema.DataType must have a "type" property (reference to schema or XLR type).",
105
+ "range": {
106
+ "end": {
107
+ "character": 7,
108
+ "line": 10,
109
+ },
110
+ "start": {
111
+ "character": 21,
112
+ "line": 8,
113
+ },
114
+ },
115
+ "severity": 1,
116
+ },
117
+ ]
118
+ `);
119
+ });
120
+
121
+ test("reports error when type is not a string", async () => {
122
+ const document = toTextDocument(
123
+ JSON.stringify(
124
+ {
125
+ id: "foo",
126
+ views: [],
127
+ navigation: { BEGIN: "FLOW1" },
128
+ schema: {
129
+ ROOT: {
130
+ application: {
131
+ type: 123,
132
+ },
133
+ },
134
+ },
135
+ },
136
+ null,
137
+ 2,
138
+ ),
139
+ );
140
+ const diagnostics = await service.validateTextDocument(document);
141
+ expect(diagnostics).toMatchInlineSnapshot(`
142
+ [
143
+ {
144
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
145
+ "range": {
146
+ "end": {
147
+ "character": 7,
148
+ "line": 10,
149
+ },
150
+ "start": {
151
+ "character": 21,
152
+ "line": 8,
153
+ },
154
+ },
155
+ "severity": 1,
156
+ },
157
+ {
158
+ "message": "Schema Validation Error: Schema "type" must be a string (schema type name or XLR type name).",
159
+ "range": {
160
+ "end": {
161
+ "character": 19,
162
+ "line": 9,
163
+ },
164
+ "start": {
165
+ "character": 16,
166
+ "line": 9,
167
+ },
168
+ },
169
+ "severity": 1,
170
+ },
171
+ ]
172
+ `);
173
+ });
174
+
175
+ test("reports error when isArray is not a boolean", async () => {
176
+ const document = toTextDocument(
177
+ JSON.stringify(
178
+ {
179
+ id: "foo",
180
+ views: [],
181
+ navigation: { BEGIN: "FLOW1" },
182
+ schema: {
183
+ ROOT: {
184
+ items: {
185
+ type: "StringType",
186
+ isArray: "yes",
187
+ },
188
+ },
189
+ },
190
+ },
191
+ null,
192
+ 2,
193
+ ),
194
+ );
195
+ const diagnostics = await service.validateTextDocument(document);
196
+ expect(diagnostics).toMatchInlineSnapshot(`
197
+ [
198
+ {
199
+ "message": "Schema Validation Error: Schema.DataType "isArray" must be a boolean.",
200
+ "range": {
201
+ "end": {
202
+ "character": 24,
203
+ "line": 10,
204
+ },
205
+ "start": {
206
+ "character": 19,
207
+ "line": 10,
208
+ },
209
+ },
210
+ "severity": 1,
211
+ },
212
+ ]
213
+ `);
214
+ });
215
+
216
+ test("reports error when validation is not an array", async () => {
217
+ const document = toTextDocument(
218
+ JSON.stringify(
219
+ {
220
+ id: "foo",
221
+ views: [],
222
+ navigation: { BEGIN: "FLOW1" },
223
+ schema: {
224
+ ROOT: {
225
+ field: {
226
+ type: "TypeA",
227
+ validation: "not-an-array",
228
+ },
229
+ },
230
+ TypeA: {
231
+ nested: { type: "StringType" },
232
+ },
233
+ },
234
+ },
235
+ null,
236
+ 2,
237
+ ),
238
+ );
239
+ const diagnostics = await service.validateTextDocument(document);
240
+ expect(diagnostics).toMatchInlineSnapshot(`
241
+ [
242
+ {
243
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
244
+ "range": {
245
+ "end": {
246
+ "character": 7,
247
+ "line": 11,
248
+ },
249
+ "start": {
250
+ "character": 15,
251
+ "line": 8,
252
+ },
253
+ },
254
+ "severity": 1,
255
+ },
256
+ ]
257
+ `);
258
+ });
259
+
260
+ test("reports error when format is not an object", async () => {
261
+ const document = toTextDocument(
262
+ JSON.stringify(
263
+ {
264
+ id: "foo",
265
+ views: [],
266
+ navigation: { BEGIN: "FLOW1" },
267
+ schema: {
268
+ ROOT: {
269
+ field: {
270
+ type: "TypeA",
271
+ format: "not-an-object",
272
+ },
273
+ },
274
+ TypeA: {
275
+ nested: { type: "StringType" },
276
+ },
277
+ },
278
+ },
279
+ null,
280
+ 2,
281
+ ),
282
+ );
283
+ const diagnostics = await service.validateTextDocument(document);
284
+ expect(diagnostics).toMatchInlineSnapshot(`
285
+ [
286
+ {
287
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
288
+ "range": {
289
+ "end": {
290
+ "character": 7,
291
+ "line": 11,
292
+ },
293
+ "start": {
294
+ "character": 15,
295
+ "line": 8,
296
+ },
297
+ },
298
+ "severity": 1,
299
+ },
300
+ ]
301
+ `);
302
+ });
303
+
304
+ test("reports error when isRecord is not a boolean", async () => {
305
+ const document = toTextDocument(
306
+ JSON.stringify(
307
+ {
308
+ id: "foo",
309
+ views: [],
310
+ navigation: { BEGIN: "FLOW1" },
311
+ schema: {
312
+ ROOT: {
313
+ field: {
314
+ type: "StringType",
315
+ isRecord: "yes",
316
+ },
317
+ },
318
+ },
319
+ },
320
+ null,
321
+ 2,
322
+ ),
323
+ );
324
+ const diagnostics = await service.validateTextDocument(document);
325
+ expect(diagnostics).toMatchInlineSnapshot(`
326
+ [
327
+ {
328
+ "message": "Schema Validation Error: Schema.DataType "isRecord" must be a boolean.",
329
+ "range": {
330
+ "end": {
331
+ "character": 25,
332
+ "line": 10,
333
+ },
334
+ "start": {
335
+ "character": 20,
336
+ "line": 10,
337
+ },
338
+ },
339
+ "severity": 1,
340
+ },
341
+ ]
342
+ `);
343
+ });
344
+
345
+ test("reports error when both isArray and isRecord are true", async () => {
346
+ const document = toTextDocument(
347
+ JSON.stringify(
348
+ {
349
+ id: "foo",
350
+ views: [],
351
+ navigation: { BEGIN: "FLOW1" },
352
+ schema: {
353
+ ROOT: {
354
+ field: {
355
+ type: "StringType",
356
+ isArray: true,
357
+ isRecord: true,
358
+ },
359
+ },
360
+ },
361
+ },
362
+ null,
363
+ 2,
364
+ ),
365
+ );
366
+ const diagnostics = await service.validateTextDocument(document);
367
+ expect(diagnostics).toMatchInlineSnapshot(`
368
+ [
369
+ {
370
+ "message": "Schema Validation Error: Schema.DataType cannot have both "isArray" and "isRecord" true.",
371
+ "range": {
372
+ "end": {
373
+ "character": 7,
374
+ "line": 12,
375
+ },
376
+ "start": {
377
+ "character": 15,
378
+ "line": 8,
379
+ },
380
+ },
381
+ "severity": 1,
382
+ },
383
+ ]
384
+ `);
385
+ });
386
+ });
387
+
388
+ describe("schema type reference validation", () => {
389
+ test("reports error for unknown type reference (not in schema, not in XLR)", async () => {
390
+ const document = toTextDocument(
391
+ JSON.stringify(
392
+ {
393
+ id: "foo",
394
+ views: [],
395
+ navigation: { BEGIN: "FLOW1" },
396
+ schema: {
397
+ ROOT: {
398
+ foo: {
399
+ type: "NonExistentXLRType",
400
+ },
401
+ },
402
+ },
403
+ },
404
+ null,
405
+ 2,
406
+ ),
407
+ );
408
+ const diagnostics = await service.validateTextDocument(document);
409
+ expect(diagnostics).toMatchInlineSnapshot(`
410
+ [
411
+ {
412
+ "message": "Schema Validation Error: Unknown schema type "NonExistentXLRType". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.",
413
+ "range": {
414
+ "end": {
415
+ "character": 36,
416
+ "line": 9,
417
+ },
418
+ "start": {
419
+ "character": 16,
420
+ "line": 9,
421
+ },
422
+ },
423
+ "severity": 1,
424
+ },
425
+ ]
426
+ `);
427
+ });
428
+
429
+ test("accepts type reference to XLR-loaded type when that type is in the SDK", async () => {
430
+ const document = toTextDocument(
431
+ JSON.stringify(
432
+ {
433
+ id: "foo",
434
+ views: [],
435
+ navigation: { BEGIN: "FLOW1" },
436
+ schema: {
437
+ ROOT: {
438
+ name: { type: "StringType" },
439
+ },
440
+ },
441
+ },
442
+ null,
443
+ 2,
444
+ ),
445
+ );
446
+ const diagnostics = await service.validateTextDocument(document);
447
+ expect(diagnostics).toMatchInlineSnapshot(`[]`);
448
+ });
449
+
450
+ test("reports errors for multiple unknown type references", async () => {
451
+ const document = toTextDocument(
452
+ JSON.stringify(
453
+ {
454
+ id: "foo",
455
+ views: [],
456
+ navigation: { BEGIN: "FLOW1" },
457
+ schema: {
458
+ ROOT: {
459
+ a: { type: "UnknownA" },
460
+ b: { type: "UnknownB" },
461
+ },
462
+ },
463
+ },
464
+ null,
465
+ 2,
466
+ ),
467
+ );
468
+ const diagnostics = await service.validateTextDocument(document);
469
+ expect(diagnostics).toMatchInlineSnapshot(`
470
+ [
471
+ {
472
+ "message": "Schema Validation Error: Unknown schema type "UnknownA". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.",
473
+ "range": {
474
+ "end": {
475
+ "character": 26,
476
+ "line": 9,
477
+ },
478
+ "start": {
479
+ "character": 16,
480
+ "line": 9,
481
+ },
482
+ },
483
+ "severity": 1,
484
+ },
485
+ {
486
+ "message": "Schema Validation Error: Unknown schema type "UnknownB". Type must be a schema type (key in this schema) or an XLR type loaded in the SDK.",
487
+ "range": {
488
+ "end": {
489
+ "character": 26,
490
+ "line": 12,
491
+ },
492
+ "start": {
493
+ "character": 16,
494
+ "line": 12,
495
+ },
496
+ },
497
+ "severity": 1,
498
+ },
499
+ ]
500
+ `);
501
+ });
502
+
503
+ test("reports no Schema DataType errors when DataType conforms to XLR (StringType)", async () => {
504
+ const document = toTextDocument(
505
+ JSON.stringify(
506
+ {
507
+ id: "foo",
508
+ views: [],
509
+ navigation: { BEGIN: "FLOW1" },
510
+ schema: {
511
+ ROOT: {
512
+ name: {
513
+ type: "StringType",
514
+ default: "",
515
+ validation: [],
516
+ format: { type: "string" },
517
+ },
518
+ },
519
+ },
520
+ },
521
+ null,
522
+ 2,
523
+ ),
524
+ );
525
+ const diagnostics = await service.validateTextDocument(document);
526
+ expect(diagnostics).toMatchInlineSnapshot(`[]`);
527
+ });
528
+
529
+ test("reports Schema DataType error when BooleanType payload has wrong type for property", async () => {
530
+ const document = toTextDocument(
531
+ JSON.stringify(
532
+ {
533
+ id: "foo",
534
+ views: [],
535
+ navigation: { BEGIN: "FLOW1" },
536
+ schema: {
537
+ ROOT: {
538
+ flag: {
539
+ type: "BooleanType",
540
+ default: "not-a-boolean",
541
+ },
542
+ },
543
+ },
544
+ },
545
+ null,
546
+ 2,
547
+ ),
548
+ );
549
+ const diagnostics = await service.validateTextDocument(document);
550
+ expect(diagnostics).toMatchInlineSnapshot(`
551
+ [
552
+ {
553
+ "message": "Schema Validation Error: Default value doesn't match the expected type of boolean for type BooleanType",
554
+ "range": {
555
+ "end": {
556
+ "character": 34,
557
+ "line": 10,
558
+ },
559
+ "start": {
560
+ "character": 19,
561
+ "line": 10,
562
+ },
563
+ },
564
+ "severity": 1,
565
+ },
566
+ ]
567
+ `);
568
+ });
569
+
570
+ describe("default property validation (CollectionType / Or type)", () => {
571
+ test("accepts CollectionType with valid default (number)", async () => {
572
+ const document = toTextDocument(
573
+ JSON.stringify(
574
+ {
575
+ id: "foo",
576
+ views: [],
577
+ navigation: { BEGIN: "FLOW1" },
578
+ schema: {
579
+ ROOT: {
580
+ items: {
581
+ type: "CollectionType",
582
+ default: 0,
583
+ validation: [{ type: "collection" }],
584
+ },
585
+ },
586
+ },
587
+ },
588
+ null,
589
+ 2,
590
+ ),
591
+ );
592
+ const diagnostics = await service.validateTextDocument(document);
593
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
594
+ d.message.includes("Schema Validation Error:"),
595
+ );
596
+ expect(schemaErrors).toHaveLength(0);
597
+ });
598
+
599
+ test("accepts CollectionType with valid default (string)", async () => {
600
+ const document = toTextDocument(
601
+ JSON.stringify(
602
+ {
603
+ id: "foo",
604
+ views: [],
605
+ navigation: { BEGIN: "FLOW1" },
606
+ schema: {
607
+ ROOT: {
608
+ items: {
609
+ type: "CollectionType",
610
+ default: "",
611
+ validation: [{ type: "collection" }],
612
+ },
613
+ },
614
+ },
615
+ },
616
+ null,
617
+ 2,
618
+ ),
619
+ );
620
+ const diagnostics = await service.validateTextDocument(document);
621
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
622
+ d.message.includes("Schema Validation Error:"),
623
+ );
624
+ expect(schemaErrors).toHaveLength(0);
625
+ });
626
+
627
+ test("reports error when CollectionType default is boolean (not in Or type)", async () => {
628
+ const document = toTextDocument(
629
+ JSON.stringify(
630
+ {
631
+ id: "foo",
632
+ views: [],
633
+ navigation: { BEGIN: "FLOW1" },
634
+ schema: {
635
+ ROOT: {
636
+ items: {
637
+ type: "CollectionType",
638
+ default: true,
639
+ validation: [{ type: "collection" }],
640
+ },
641
+ },
642
+ },
643
+ },
644
+ null,
645
+ 2,
646
+ ),
647
+ );
648
+ const diagnostics = await service.validateTextDocument(document);
649
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
650
+ d.message.includes("Schema Validation Error:"),
651
+ );
652
+ expect(schemaErrors).toHaveLength(1);
653
+ expect(schemaErrors[0].message).toBe(
654
+ "Schema Validation Error: Default value doesn't match any of the expected types number, string for type CollectionType",
655
+ );
656
+ });
657
+
658
+ test("reports error when CollectionType default is array (not in Or type)", async () => {
659
+ const document = toTextDocument(
660
+ JSON.stringify(
661
+ {
662
+ id: "foo",
663
+ views: [],
664
+ navigation: { BEGIN: "FLOW1" },
665
+ schema: {
666
+ ROOT: {
667
+ items: {
668
+ type: "CollectionType",
669
+ default: [],
670
+ validation: [{ type: "collection" }],
671
+ },
672
+ },
673
+ },
674
+ },
675
+ null,
676
+ 2,
677
+ ),
678
+ );
679
+ const diagnostics = await service.validateTextDocument(document);
680
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
681
+ d.message.includes("Schema Validation Error:"),
682
+ );
683
+ expect(schemaErrors).toHaveLength(1);
684
+ expect(schemaErrors[0].message).toBe(
685
+ "Schema Validation Error: Default value doesn't match any of the expected types number, string for type CollectionType",
686
+ );
687
+ });
688
+
689
+ test("reports error when CollectionType default is object (not in Or type)", async () => {
690
+ const document = toTextDocument(
691
+ JSON.stringify(
692
+ {
693
+ id: "foo",
694
+ views: [],
695
+ navigation: { BEGIN: "FLOW1" },
696
+ schema: {
697
+ ROOT: {
698
+ items: {
699
+ type: "CollectionType",
700
+ default: {},
701
+ validation: [{ type: "collection" }],
702
+ },
703
+ },
704
+ },
705
+ },
706
+ null,
707
+ 2,
708
+ ),
709
+ );
710
+ const diagnostics = await service.validateTextDocument(document);
711
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
712
+ d.message.includes("Schema Validation Error:"),
713
+ );
714
+ expect(schemaErrors).toHaveLength(1);
715
+ expect(schemaErrors[0].message).toBe(
716
+ "Schema Validation Error: Default value doesn't match any of the expected types number, string for type CollectionType",
717
+ );
718
+ });
719
+ });
720
+ });
721
+
722
+ describe("flow without schema", () => {
723
+ test("does not add schema errors when schema is absent", async () => {
724
+ const document = toTextDocument(
725
+ JSON.stringify(
726
+ {
727
+ id: "foo",
728
+ views: [],
729
+ navigation: { BEGIN: "FLOW1" },
730
+ },
731
+ null,
732
+ 2,
733
+ ),
734
+ );
735
+ const diagnostics = await service.validateTextDocument(document);
736
+ expect(diagnostics).toMatchInlineSnapshot(`[]`);
737
+ });
738
+ });
739
+
740
+ describe("validation object logic branches", () => {
741
+ test('reports error when validation entry is missing "type" property', async () => {
742
+ const document = toTextDocument(
743
+ JSON.stringify(
744
+ {
745
+ id: "foo",
746
+ views: [],
747
+ navigation: { BEGIN: "FLOW1" },
748
+ schema: {
749
+ ROOT: {
750
+ field: {
751
+ type: "StringType",
752
+ validation: [{}],
753
+ format: { type: "string" },
754
+ },
755
+ },
756
+ },
757
+ },
758
+ null,
759
+ 2,
760
+ ),
761
+ );
762
+ const diagnostics = await service.validateTextDocument(document);
763
+ expect(diagnostics).toMatchInlineSnapshot(`
764
+ [
765
+ {
766
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
767
+ "range": {
768
+ "end": {
769
+ "character": 7,
770
+ "line": 16,
771
+ },
772
+ "start": {
773
+ "character": 15,
774
+ "line": 8,
775
+ },
776
+ },
777
+ "severity": 1,
778
+ },
779
+ {
780
+ "message": "Schema Validation Error: Validation object missing "type" property",
781
+ "range": {
782
+ "end": {
783
+ "character": 12,
784
+ "line": 11,
785
+ },
786
+ "start": {
787
+ "character": 10,
788
+ "line": 11,
789
+ },
790
+ },
791
+ "severity": 1,
792
+ },
793
+ ]
794
+ `);
795
+ });
796
+
797
+ test("reports error when validation type is not a string", async () => {
798
+ const document = toTextDocument(
799
+ JSON.stringify(
800
+ {
801
+ id: "foo",
802
+ views: [],
803
+ navigation: { BEGIN: "FLOW1" },
804
+ schema: {
805
+ ROOT: {
806
+ field: {
807
+ type: "StringType",
808
+ validation: [{ type: 123 }],
809
+ format: { type: "string" },
810
+ },
811
+ },
812
+ },
813
+ },
814
+ null,
815
+ 2,
816
+ ),
817
+ );
818
+ const diagnostics = await service.validateTextDocument(document);
819
+ expect(diagnostics).toMatchInlineSnapshot(`
820
+ [
821
+ {
822
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
823
+ "range": {
824
+ "end": {
825
+ "character": 7,
826
+ "line": 18,
827
+ },
828
+ "start": {
829
+ "character": 15,
830
+ "line": 8,
831
+ },
832
+ },
833
+ "severity": 1,
834
+ },
835
+ {
836
+ "message": "Schema Validation Error: Validation type must be a string",
837
+ "range": {
838
+ "end": {
839
+ "character": 23,
840
+ "line": 12,
841
+ },
842
+ "start": {
843
+ "character": 20,
844
+ "line": 12,
845
+ },
846
+ },
847
+ "severity": 1,
848
+ },
849
+ ]
850
+ `);
851
+ });
852
+
853
+ test("reports error when validation type is not a registered validator", async () => {
854
+ const document = toTextDocument(
855
+ JSON.stringify(
856
+ {
857
+ id: "foo",
858
+ views: [],
859
+ navigation: { BEGIN: "FLOW1" },
860
+ schema: {
861
+ ROOT: {
862
+ field: {
863
+ type: "StringType",
864
+ validation: [{ type: "NonExistentValidator" }],
865
+ format: { type: "string" },
866
+ },
867
+ },
868
+ },
869
+ },
870
+ null,
871
+ 2,
872
+ ),
873
+ );
874
+ const diagnostics = await service.validateTextDocument(document);
875
+ expect(diagnostics).toMatchInlineSnapshot(`
876
+ [
877
+ {
878
+ "message": "Schema Validation Error: Validation Function [object Object] is not a registered validator",
879
+ "range": {
880
+ "end": {
881
+ "character": 11,
882
+ "line": 13,
883
+ },
884
+ "start": {
885
+ "character": 10,
886
+ "line": 11,
887
+ },
888
+ },
889
+ "severity": 1,
890
+ },
891
+ ]
892
+ `);
893
+ });
894
+
895
+ test("reports error when registered validator has invalid props (min expects number)", async () => {
896
+ const document = toTextDocument(
897
+ JSON.stringify(
898
+ {
899
+ id: "foo",
900
+ views: [],
901
+ navigation: { BEGIN: "FLOW1" },
902
+ schema: {
903
+ ROOT: {
904
+ score: {
905
+ type: "IntegerType",
906
+ validation: [{ type: "min", value: "not-a-number" }],
907
+ format: { type: "integer" },
908
+ },
909
+ },
910
+ },
911
+ },
912
+ null,
913
+ 2,
914
+ ),
915
+ );
916
+ const diagnostics = await service.validateTextDocument(document);
917
+ expect(diagnostics).toMatchInlineSnapshot(`
918
+ [
919
+ {
920
+ "message": "Schema Validation Error: Expected type "number" but got "string"",
921
+ "range": {
922
+ "end": {
923
+ "character": 11,
924
+ "line": 14,
925
+ },
926
+ "start": {
927
+ "character": 10,
928
+ "line": 11,
929
+ },
930
+ },
931
+ "severity": 1,
932
+ },
933
+ ]
934
+ `);
935
+ });
936
+
937
+ test("reports error when validation array element is not an object", async () => {
938
+ const document = toTextDocument(
939
+ JSON.stringify(
940
+ {
941
+ id: "foo",
942
+ views: [],
943
+ navigation: { BEGIN: "FLOW1" },
944
+ schema: {
945
+ ROOT: {
946
+ field: {
947
+ type: "StringType",
948
+ validation: ["not-an-object"],
949
+ format: { type: "string" },
950
+ },
951
+ },
952
+ },
953
+ },
954
+ null,
955
+ 2,
956
+ ),
957
+ );
958
+ const diagnostics = await service.validateTextDocument(document);
959
+ expect(diagnostics).toMatchInlineSnapshot(`
960
+ [
961
+ {
962
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
963
+ "range": {
964
+ "end": {
965
+ "character": 7,
966
+ "line": 16,
967
+ },
968
+ "start": {
969
+ "character": 15,
970
+ "line": 8,
971
+ },
972
+ },
973
+ "severity": 1,
974
+ },
975
+ {
976
+ "message": "Schema Validation Error: Schema.DataType "validation" must be an object.",
977
+ "range": {
978
+ "end": {
979
+ "character": 9,
980
+ "line": 12,
981
+ },
982
+ "start": {
983
+ "character": 22,
984
+ "line": 10,
985
+ },
986
+ },
987
+ "severity": 1,
988
+ },
989
+ ]
990
+ `);
991
+ });
992
+ });
993
+
994
+ describe("format object logic branches", () => {
995
+ test('reports error when format object is missing "type" property', async () => {
996
+ const document = toTextDocument(
997
+ JSON.stringify(
998
+ {
999
+ id: "foo",
1000
+ views: [],
1001
+ navigation: { BEGIN: "FLOW1" },
1002
+ schema: {
1003
+ ROOT: {
1004
+ field: {
1005
+ type: "StringType",
1006
+ validation: [],
1007
+ format: {},
1008
+ },
1009
+ },
1010
+ },
1011
+ },
1012
+ null,
1013
+ 2,
1014
+ ),
1015
+ );
1016
+ const diagnostics = await service.validateTextDocument(document);
1017
+ expect(diagnostics).toMatchInlineSnapshot(`
1018
+ [
1019
+ {
1020
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
1021
+ "range": {
1022
+ "end": {
1023
+ "character": 7,
1024
+ "line": 12,
1025
+ },
1026
+ "start": {
1027
+ "character": 15,
1028
+ "line": 8,
1029
+ },
1030
+ },
1031
+ "severity": 1,
1032
+ },
1033
+ {
1034
+ "message": "Schema Validation Error: Format object missing "type" property",
1035
+ "range": {
1036
+ "end": {
1037
+ "character": 20,
1038
+ "line": 11,
1039
+ },
1040
+ "start": {
1041
+ "character": 18,
1042
+ "line": 11,
1043
+ },
1044
+ },
1045
+ "severity": 1,
1046
+ },
1047
+ ]
1048
+ `);
1049
+ });
1050
+
1051
+ test("reports error when format type is not a string", async () => {
1052
+ const document = toTextDocument(
1053
+ JSON.stringify(
1054
+ {
1055
+ id: "foo",
1056
+ views: [],
1057
+ navigation: { BEGIN: "FLOW1" },
1058
+ schema: {
1059
+ ROOT: {
1060
+ field: {
1061
+ type: "StringType",
1062
+ validation: [],
1063
+ format: { type: 42 },
1064
+ },
1065
+ },
1066
+ },
1067
+ },
1068
+ null,
1069
+ 2,
1070
+ ),
1071
+ );
1072
+ const diagnostics = await service.validateTextDocument(document);
1073
+ expect(diagnostics).toMatchInlineSnapshot(`
1074
+ [
1075
+ {
1076
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
1077
+ "range": {
1078
+ "end": {
1079
+ "character": 7,
1080
+ "line": 14,
1081
+ },
1082
+ "start": {
1083
+ "character": 15,
1084
+ "line": 8,
1085
+ },
1086
+ },
1087
+ "severity": 1,
1088
+ },
1089
+ {
1090
+ "message": "Schema Validation Error: Format type must be a string",
1091
+ "range": {
1092
+ "end": {
1093
+ "character": 20,
1094
+ "line": 12,
1095
+ },
1096
+ "start": {
1097
+ "character": 18,
1098
+ "line": 12,
1099
+ },
1100
+ },
1101
+ "severity": 1,
1102
+ },
1103
+ ]
1104
+ `);
1105
+ });
1106
+
1107
+ test("reports error when format type is not a registered formatter", async () => {
1108
+ const document = toTextDocument(
1109
+ JSON.stringify(
1110
+ {
1111
+ id: "foo",
1112
+ views: [],
1113
+ navigation: { BEGIN: "FLOW1" },
1114
+ schema: {
1115
+ ROOT: {
1116
+ field: {
1117
+ type: "StringType",
1118
+ validation: [],
1119
+ format: { type: "NonExistentFormatter" },
1120
+ },
1121
+ },
1122
+ },
1123
+ },
1124
+ null,
1125
+ 2,
1126
+ ),
1127
+ );
1128
+ const diagnostics = await service.validateTextDocument(document);
1129
+ expect(diagnostics).toMatchInlineSnapshot(`
1130
+ [
1131
+ {
1132
+ "message": "Schema Validation Error: Formatter [object Object] is not a registered formatter",
1133
+ "range": {
1134
+ "end": {
1135
+ "character": 9,
1136
+ "line": 13,
1137
+ },
1138
+ "start": {
1139
+ "character": 18,
1140
+ "line": 11,
1141
+ },
1142
+ },
1143
+ "severity": 1,
1144
+ },
1145
+ ]
1146
+ `);
1147
+ });
1148
+
1149
+ test("reports error when formatter with 3 args has invalid props (date mask must be string)", async () => {
1150
+ const document = toTextDocument(
1151
+ JSON.stringify(
1152
+ {
1153
+ id: "foo",
1154
+ views: [],
1155
+ navigation: { BEGIN: "FLOW1" },
1156
+ schema: {
1157
+ ROOT: {
1158
+ birthDate: {
1159
+ type: "DateType",
1160
+ validation: [],
1161
+ format: { type: "date", mask: 123 },
1162
+ },
1163
+ },
1164
+ },
1165
+ },
1166
+ null,
1167
+ 2,
1168
+ ),
1169
+ );
1170
+ const diagnostics = await service.validateTextDocument(document);
1171
+ expect(diagnostics).toMatchInlineSnapshot(`
1172
+ [
1173
+ {
1174
+ "message": "Schema Validation Error: Expected type "string" but got "number"",
1175
+ "range": {
1176
+ "end": {
1177
+ "character": 9,
1178
+ "line": 14,
1179
+ },
1180
+ "start": {
1181
+ "character": 18,
1182
+ "line": 11,
1183
+ },
1184
+ },
1185
+ "severity": 1,
1186
+ },
1187
+ ]
1188
+ `);
1189
+ });
1190
+
1191
+ test("accepts formatter with 3 args and valid props (date with mask string)", async () => {
1192
+ const document = toTextDocument(
1193
+ JSON.stringify(
1194
+ {
1195
+ id: "foo",
1196
+ views: [],
1197
+ navigation: { BEGIN: "FLOW1" },
1198
+ schema: {
1199
+ ROOT: {
1200
+ birthDate: {
1201
+ type: "DateType",
1202
+ validation: [],
1203
+ format: { type: "date", mask: "MM/DD/YYYY" },
1204
+ },
1205
+ },
1206
+ },
1207
+ },
1208
+ null,
1209
+ 2,
1210
+ ),
1211
+ );
1212
+ const diagnostics = await service.validateTextDocument(document);
1213
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1214
+ d.message.includes("Schema Validation Error:"),
1215
+ );
1216
+ expect(schemaErrors).toHaveLength(0);
1217
+ });
1218
+ });
1219
+
1220
+ describe("schema and flow structure branches", () => {
1221
+ test("reports error when flow schema is not an object (string)", async () => {
1222
+ const document = toTextDocument(
1223
+ JSON.stringify(
1224
+ {
1225
+ id: "foo",
1226
+ views: [],
1227
+ navigation: { BEGIN: "FLOW1" },
1228
+ schema: "not-an-object",
1229
+ },
1230
+ null,
1231
+ 2,
1232
+ ),
1233
+ );
1234
+ const diagnostics = await service.validateTextDocument(document);
1235
+ expect(diagnostics).toMatchInlineSnapshot(`
1236
+ [
1237
+ {
1238
+ "message": "Content Validation Error - type: Expected an object but got an "string"",
1239
+ "range": {
1240
+ "end": {
1241
+ "character": 27,
1242
+ "line": 6,
1243
+ },
1244
+ "start": {
1245
+ "character": 12,
1246
+ "line": 6,
1247
+ },
1248
+ },
1249
+ "severity": 1,
1250
+ },
1251
+ {
1252
+ "message": "Schema Validation Error: Flow "schema" must be an object with at least a "ROOT" key.",
1253
+ "range": {
1254
+ "end": {
1255
+ "character": 27,
1256
+ "line": 6,
1257
+ },
1258
+ "start": {
1259
+ "character": 12,
1260
+ "line": 6,
1261
+ },
1262
+ },
1263
+ "severity": 1,
1264
+ },
1265
+ ]
1266
+ `);
1267
+ });
1268
+
1269
+ test("reports error when flow schema is not an object (array)", async () => {
1270
+ const document = toTextDocument(
1271
+ JSON.stringify(
1272
+ {
1273
+ id: "foo",
1274
+ views: [],
1275
+ navigation: { BEGIN: "FLOW1" },
1276
+ schema: [],
1277
+ },
1278
+ null,
1279
+ 2,
1280
+ ),
1281
+ );
1282
+ const diagnostics = await service.validateTextDocument(document);
1283
+ expect(diagnostics).toMatchInlineSnapshot(`
1284
+ [
1285
+ {
1286
+ "message": "Content Validation Error - type: Expected an object but got an "array"",
1287
+ "range": {
1288
+ "end": {
1289
+ "character": 14,
1290
+ "line": 6,
1291
+ },
1292
+ "start": {
1293
+ "character": 12,
1294
+ "line": 6,
1295
+ },
1296
+ },
1297
+ "severity": 1,
1298
+ },
1299
+ {
1300
+ "message": "Schema Validation Error: Flow "schema" must be an object with at least a "ROOT" key.",
1301
+ "range": {
1302
+ "end": {
1303
+ "character": 14,
1304
+ "line": 6,
1305
+ },
1306
+ "start": {
1307
+ "character": 12,
1308
+ "line": 6,
1309
+ },
1310
+ },
1311
+ "severity": 1,
1312
+ },
1313
+ ]
1314
+ `);
1315
+ });
1316
+
1317
+ test("reports error when schema node (ROOT) value is not an object", async () => {
1318
+ const document = toTextDocument(
1319
+ JSON.stringify(
1320
+ {
1321
+ id: "foo",
1322
+ views: [],
1323
+ navigation: { BEGIN: "FLOW1" },
1324
+ schema: {
1325
+ ROOT: "not-an-object",
1326
+ },
1327
+ },
1328
+ null,
1329
+ 2,
1330
+ ),
1331
+ );
1332
+ const diagnostics = await service.validateTextDocument(document);
1333
+ expect(diagnostics).toMatchInlineSnapshot(`
1334
+ [
1335
+ {
1336
+ "message": "Content Validation Error - type: Expected an object but got an "string"",
1337
+ "range": {
1338
+ "end": {
1339
+ "character": 27,
1340
+ "line": 7,
1341
+ },
1342
+ "start": {
1343
+ "character": 12,
1344
+ "line": 7,
1345
+ },
1346
+ },
1347
+ "severity": 1,
1348
+ },
1349
+ {
1350
+ "message": "Schema Validation Error: Schema node "ROOT" must be an object.",
1351
+ "range": {
1352
+ "end": {
1353
+ "character": 27,
1354
+ "line": 7,
1355
+ },
1356
+ "start": {
1357
+ "character": 12,
1358
+ "line": 7,
1359
+ },
1360
+ },
1361
+ "severity": 1,
1362
+ },
1363
+ ]
1364
+ `);
1365
+ });
1366
+
1367
+ test("reports error when schema property value is not an object (DataType)", async () => {
1368
+ const document = toTextDocument(
1369
+ JSON.stringify(
1370
+ {
1371
+ id: "foo",
1372
+ views: [],
1373
+ navigation: { BEGIN: "FLOW1" },
1374
+ schema: {
1375
+ ROOT: {
1376
+ application: "must-be-DataType-object",
1377
+ },
1378
+ },
1379
+ },
1380
+ null,
1381
+ 2,
1382
+ ),
1383
+ );
1384
+ const diagnostics = await service.validateTextDocument(document);
1385
+ expect(diagnostics).toMatchInlineSnapshot(`
1386
+ [
1387
+ {
1388
+ "message": "Content Validation Error - value: Does not match any of the expected types for type: 'DataTypes'",
1389
+ "range": {
1390
+ "end": {
1391
+ "character": 46,
1392
+ "line": 8,
1393
+ },
1394
+ "start": {
1395
+ "character": 21,
1396
+ "line": 8,
1397
+ },
1398
+ },
1399
+ "severity": 1,
1400
+ },
1401
+ {
1402
+ "message": "Schema Validation Error: Schema property "application" must be an object (Schema.DataType) with a "type" field.",
1403
+ "range": {
1404
+ "end": {
1405
+ "character": 46,
1406
+ "line": 8,
1407
+ },
1408
+ "start": {
1409
+ "character": 21,
1410
+ "line": 8,
1411
+ },
1412
+ },
1413
+ "severity": 1,
1414
+ },
1415
+ ]
1416
+ `);
1417
+ });
1418
+ });
1419
+
1420
+ describe("specific DataTypes from CommonTypes", () => {
1421
+ test("accepts IntegerType with valid validation and format", async () => {
1422
+ const document = toTextDocument(
1423
+ JSON.stringify(
1424
+ {
1425
+ id: "foo",
1426
+ views: [],
1427
+ navigation: { BEGIN: "FLOW1" },
1428
+ schema: {
1429
+ ROOT: {
1430
+ count: {
1431
+ type: "IntegerType",
1432
+ validation: [{ type: "integer" }],
1433
+ format: { type: "integer" },
1434
+ },
1435
+ },
1436
+ },
1437
+ },
1438
+ null,
1439
+ 2,
1440
+ ),
1441
+ );
1442
+ const diagnostics = await service.validateTextDocument(document);
1443
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1444
+ d.message.includes("Schema Validation Error:"),
1445
+ );
1446
+ expect(schemaErrors).toHaveLength(0);
1447
+ });
1448
+
1449
+ test("accepts BooleanType with valid default and validation", async () => {
1450
+ const document = toTextDocument(
1451
+ JSON.stringify(
1452
+ {
1453
+ id: "foo",
1454
+ views: [],
1455
+ navigation: { BEGIN: "FLOW1" },
1456
+ schema: {
1457
+ ROOT: {
1458
+ flag: {
1459
+ type: "BooleanType",
1460
+ default: false,
1461
+ validation: [
1462
+ {
1463
+ type: "oneOf",
1464
+ message: "Value must be true or false",
1465
+ options: [true, false],
1466
+ },
1467
+ ],
1468
+ format: { type: "string" },
1469
+ },
1470
+ },
1471
+ },
1472
+ },
1473
+ null,
1474
+ 2,
1475
+ ),
1476
+ );
1477
+ const diagnostics = await service.validateTextDocument(document);
1478
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1479
+ d.message.includes("Schema Validation Error:"),
1480
+ );
1481
+ expect(schemaErrors).toHaveLength(0);
1482
+ });
1483
+
1484
+ test("accepts DateType with valid format", async () => {
1485
+ const document = toTextDocument(
1486
+ JSON.stringify(
1487
+ {
1488
+ id: "foo",
1489
+ views: [],
1490
+ navigation: { BEGIN: "FLOW1" },
1491
+ schema: {
1492
+ ROOT: {
1493
+ start: {
1494
+ type: "DateType",
1495
+ validation: [],
1496
+ format: { type: "date" },
1497
+ },
1498
+ },
1499
+ },
1500
+ },
1501
+ null,
1502
+ 2,
1503
+ ),
1504
+ );
1505
+ const diagnostics = await service.validateTextDocument(document);
1506
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1507
+ d.message.includes("Schema Validation Error:"),
1508
+ );
1509
+ expect(schemaErrors).toHaveLength(0);
1510
+ });
1511
+
1512
+ test("accepts formatter with 2 generic args (integer) without prop validation", async () => {
1513
+ const document = toTextDocument(
1514
+ JSON.stringify(
1515
+ {
1516
+ id: "foo",
1517
+ views: [],
1518
+ navigation: { BEGIN: "FLOW1" },
1519
+ schema: {
1520
+ ROOT: {
1521
+ num: {
1522
+ type: "IntegerType",
1523
+ validation: [],
1524
+ format: { type: "integer" },
1525
+ },
1526
+ },
1527
+ },
1528
+ },
1529
+ null,
1530
+ 2,
1531
+ ),
1532
+ );
1533
+ const diagnostics = await service.validateTextDocument(document);
1534
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1535
+ d.message.includes("Schema Validation Error:"),
1536
+ );
1537
+ expect(schemaErrors).toHaveLength(0);
1538
+ });
1539
+ });
1540
+
1541
+ describe("specific Validators from CommonTypes", () => {
1542
+ test("accepts required validator with no props", async () => {
1543
+ const document = toTextDocument(
1544
+ JSON.stringify(
1545
+ {
1546
+ id: "foo",
1547
+ views: [],
1548
+ navigation: { BEGIN: "FLOW1" },
1549
+ schema: {
1550
+ ROOT: {
1551
+ name: {
1552
+ type: "StringType",
1553
+ validation: [{ type: "required" }],
1554
+ format: { type: "string" },
1555
+ },
1556
+ },
1557
+ },
1558
+ },
1559
+ null,
1560
+ 2,
1561
+ ),
1562
+ );
1563
+ const diagnostics = await service.validateTextDocument(document);
1564
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1565
+ d.message.includes("Schema Validation Error:"),
1566
+ );
1567
+ expect(schemaErrors).toHaveLength(0);
1568
+ });
1569
+
1570
+ test("accepts string validator (no props)", async () => {
1571
+ const document = toTextDocument(
1572
+ JSON.stringify(
1573
+ {
1574
+ id: "foo",
1575
+ views: [],
1576
+ navigation: { BEGIN: "FLOW1" },
1577
+ schema: {
1578
+ ROOT: {
1579
+ name: {
1580
+ type: "StringType",
1581
+ validation: [{ type: "string" }],
1582
+ format: { type: "string" },
1583
+ },
1584
+ },
1585
+ },
1586
+ },
1587
+ null,
1588
+ 2,
1589
+ ),
1590
+ );
1591
+ const diagnostics = await service.validateTextDocument(document);
1592
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1593
+ d.message.includes("Schema Validation Error:"),
1594
+ );
1595
+ expect(schemaErrors).toHaveLength(0);
1596
+ });
1597
+
1598
+ test("accepts min validator with valid number prop", async () => {
1599
+ const document = toTextDocument(
1600
+ JSON.stringify(
1601
+ {
1602
+ id: "foo",
1603
+ views: [],
1604
+ navigation: { BEGIN: "FLOW1" },
1605
+ schema: {
1606
+ ROOT: {
1607
+ score: {
1608
+ type: "IntegerType",
1609
+ validation: [{ type: "min", value: 0 }],
1610
+ format: { type: "integer" },
1611
+ },
1612
+ },
1613
+ },
1614
+ },
1615
+ null,
1616
+ 2,
1617
+ ),
1618
+ );
1619
+ const diagnostics = await service.validateTextDocument(document);
1620
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1621
+ d.message.includes("Schema Validation Error:"),
1622
+ );
1623
+ expect(schemaErrors).toHaveLength(0);
1624
+ });
1625
+
1626
+ describe("length validator (OrType / union params)", () => {
1627
+ test("accepts length validator with min/max variant (valid props)", async () => {
1628
+ const document = toTextDocument(
1629
+ JSON.stringify(
1630
+ {
1631
+ id: "foo",
1632
+ views: [],
1633
+ navigation: { BEGIN: "FLOW1" },
1634
+ schema: {
1635
+ ROOT: {
1636
+ title: {
1637
+ type: "StringType",
1638
+ validation: [{ type: "length", min: 1, max: 100 }],
1639
+ format: { type: "string" },
1640
+ },
1641
+ },
1642
+ },
1643
+ },
1644
+ null,
1645
+ 2,
1646
+ ),
1647
+ );
1648
+ const diagnostics = await service.validateTextDocument(document);
1649
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1650
+ d.message.includes("Schema Validation Error:"),
1651
+ );
1652
+ expect(schemaErrors).toHaveLength(0);
1653
+ });
1654
+
1655
+ test("accepts length validator with min only (first Or variant)", async () => {
1656
+ const document = toTextDocument(
1657
+ JSON.stringify(
1658
+ {
1659
+ id: "foo",
1660
+ views: [],
1661
+ navigation: { BEGIN: "FLOW1" },
1662
+ schema: {
1663
+ ROOT: {
1664
+ title: {
1665
+ type: "StringType",
1666
+ validation: [{ type: "length", min: 0 }],
1667
+ format: { type: "string" },
1668
+ },
1669
+ },
1670
+ },
1671
+ },
1672
+ null,
1673
+ 2,
1674
+ ),
1675
+ );
1676
+ const diagnostics = await service.validateTextDocument(document);
1677
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1678
+ d.message.includes("Schema Validation Error:"),
1679
+ );
1680
+ expect(schemaErrors).toHaveLength(0);
1681
+ });
1682
+
1683
+ test("accepts length validator with max only (first Or variant)", async () => {
1684
+ const document = toTextDocument(
1685
+ JSON.stringify(
1686
+ {
1687
+ id: "foo",
1688
+ views: [],
1689
+ navigation: { BEGIN: "FLOW1" },
1690
+ schema: {
1691
+ ROOT: {
1692
+ code: {
1693
+ type: "StringType",
1694
+ validation: [{ type: "length", max: 10 }],
1695
+ format: { type: "string" },
1696
+ },
1697
+ },
1698
+ },
1699
+ },
1700
+ null,
1701
+ 2,
1702
+ ),
1703
+ );
1704
+ const diagnostics = await service.validateTextDocument(document);
1705
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1706
+ d.message.includes("Schema Validation Error:"),
1707
+ );
1708
+ expect(schemaErrors).toHaveLength(0);
1709
+ });
1710
+
1711
+ test("accepts length validator with exact variant (second Or variant)", async () => {
1712
+ const document = toTextDocument(
1713
+ JSON.stringify(
1714
+ {
1715
+ id: "foo",
1716
+ views: [],
1717
+ navigation: { BEGIN: "FLOW1" },
1718
+ schema: {
1719
+ ROOT: {
1720
+ pin: {
1721
+ type: "StringType",
1722
+ validation: [{ type: "length", exact: 6 }],
1723
+ format: { type: "string" },
1724
+ },
1725
+ },
1726
+ },
1727
+ },
1728
+ null,
1729
+ 2,
1730
+ ),
1731
+ );
1732
+ const diagnostics = await service.validateTextDocument(document);
1733
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1734
+ d.message.includes("Schema Validation Error:"),
1735
+ );
1736
+ expect(schemaErrors).toHaveLength(0);
1737
+ });
1738
+
1739
+ test("reports error when length validator has invalid prop type (min must be number)", async () => {
1740
+ const document = toTextDocument(
1741
+ JSON.stringify(
1742
+ {
1743
+ id: "foo",
1744
+ views: [],
1745
+ navigation: { BEGIN: "FLOW1" },
1746
+ schema: {
1747
+ ROOT: {
1748
+ title: {
1749
+ type: "StringType",
1750
+ validation: [{ type: "length", min: "five" }],
1751
+ format: { type: "string" },
1752
+ },
1753
+ },
1754
+ },
1755
+ },
1756
+ null,
1757
+ 2,
1758
+ ),
1759
+ );
1760
+ const diagnostics = await service.validateTextDocument(document);
1761
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1762
+ d.message.includes("Schema Validation Error:"),
1763
+ );
1764
+ expect(schemaErrors.length).toBeGreaterThan(0);
1765
+ expect(
1766
+ schemaErrors.some(
1767
+ (d) =>
1768
+ d.message.includes("Expected type") ||
1769
+ d.message.includes("invalid function parameters"),
1770
+ ),
1771
+ ).toBe(true);
1772
+ });
1773
+
1774
+ test("reports error when length validator has invalid function parameters (no Or variant matches)", async () => {
1775
+ const document = toTextDocument(
1776
+ JSON.stringify(
1777
+ {
1778
+ id: "foo",
1779
+ views: [],
1780
+ navigation: { BEGIN: "FLOW1" },
1781
+ schema: {
1782
+ ROOT: {
1783
+ title: {
1784
+ type: "StringType",
1785
+ validation: [
1786
+ { type: "length", min: "not-a-number", max: "also-not" },
1787
+ ],
1788
+ format: { type: "string" },
1789
+ },
1790
+ },
1791
+ },
1792
+ },
1793
+ null,
1794
+ 2,
1795
+ ),
1796
+ );
1797
+ const diagnostics = await service.validateTextDocument(document);
1798
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1799
+ d.message.includes("Schema Validation Error:"),
1800
+ );
1801
+ expect(schemaErrors.length).toBeGreaterThan(0);
1802
+ expect(
1803
+ schemaErrors.some(
1804
+ (d) =>
1805
+ d.message.includes("invalid function parameters") ||
1806
+ d.message.includes("Expected type"),
1807
+ ),
1808
+ ).toBe(true);
1809
+ });
1810
+
1811
+ test("reports error when length validator matches multiple Or variants (ambiguous params)", async () => {
1812
+ const document = toTextDocument(
1813
+ JSON.stringify(
1814
+ {
1815
+ id: "foo",
1816
+ views: [],
1817
+ navigation: { BEGIN: "FLOW1" },
1818
+ schema: {
1819
+ ROOT: {
1820
+ field: {
1821
+ type: "StringType",
1822
+ validation: [{ type: "length", min: 1, exact: 5 }],
1823
+ format: { type: "string" },
1824
+ },
1825
+ },
1826
+ },
1827
+ },
1828
+ null,
1829
+ 2,
1830
+ ),
1831
+ );
1832
+ const diagnostics = await service.validateTextDocument(document);
1833
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1834
+ d.message.includes("Schema Validation Error:"),
1835
+ );
1836
+ expect(schemaErrors).toHaveLength(1);
1837
+ expect(schemaErrors[0].message).toContain(
1838
+ "Validation function invalid function parameters for type length",
1839
+ );
1840
+ });
1841
+ });
1842
+ });
1843
+
1844
+ describe("specific Formatters from CommonTypes", () => {
1845
+ test("accepts currency formatter with valid optional props", async () => {
1846
+ const document = toTextDocument(
1847
+ JSON.stringify(
1848
+ {
1849
+ id: "foo",
1850
+ views: [],
1851
+ navigation: { BEGIN: "FLOW1" },
1852
+ schema: {
1853
+ ROOT: {
1854
+ price: {
1855
+ type: "StringType",
1856
+ validation: [],
1857
+ format: {
1858
+ type: "currency",
1859
+ currencySymbol: "$",
1860
+ precision: 2,
1861
+ useParensForNeg: false,
1862
+ },
1863
+ },
1864
+ },
1865
+ },
1866
+ },
1867
+ null,
1868
+ 2,
1869
+ ),
1870
+ );
1871
+ const diagnostics = await service.validateTextDocument(document);
1872
+ const schemaErrors = (diagnostics ?? []).filter((d) =>
1873
+ d.message.includes("Schema Validation Error:"),
1874
+ );
1875
+ expect(schemaErrors).toHaveLength(0);
1876
+ });
1877
+
1878
+ test("reports error when currency formatter has invalid prop type (precision must be number)", async () => {
1879
+ const document = toTextDocument(
1880
+ JSON.stringify(
1881
+ {
1882
+ id: "foo",
1883
+ views: [],
1884
+ navigation: { BEGIN: "FLOW1" },
1885
+ schema: {
1886
+ ROOT: {
1887
+ price: {
1888
+ type: "StringType",
1889
+ validation: [],
1890
+ format: {
1891
+ type: "currency",
1892
+ precision: "two",
1893
+ },
1894
+ },
1895
+ },
1896
+ },
1897
+ },
1898
+ null,
1899
+ 2,
1900
+ ),
1901
+ );
1902
+ const diagnostics = await service.validateTextDocument(document);
1903
+ expect(diagnostics).toMatchInlineSnapshot(`
1904
+ [
1905
+ {
1906
+ "message": "Schema Validation Error: Expected type "number" but got "string"",
1907
+ "range": {
1908
+ "end": {
1909
+ "character": 9,
1910
+ "line": 14,
1911
+ },
1912
+ "start": {
1913
+ "character": 18,
1914
+ "line": 11,
1915
+ },
1916
+ },
1917
+ "severity": 1,
1918
+ },
1919
+ ]
1920
+ `);
1921
+ });
1922
+ });
1923
+ });