@squiz/dx-json-schema-lib 1.79.0 → 1.80.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/jest.config.ts +13 -2
  3. package/lib/JsonSchemaService.d.ts +34 -0
  4. package/lib/JsonSchemaService.js +140 -0
  5. package/lib/JsonSchemaService.js.map +1 -0
  6. package/lib/JsonSchemaService.spec.d.ts +1 -0
  7. package/lib/JsonSchemaService.spec.js +1807 -0
  8. package/lib/JsonSchemaService.spec.js.map +1 -0
  9. package/lib/JsonValidationService.d.ts +11 -33
  10. package/lib/JsonValidationService.js +43 -194
  11. package/lib/JsonValidationService.js.map +1 -1
  12. package/lib/JsonValidationService.spec.js +19 -1800
  13. package/lib/JsonValidationService.spec.js.map +1 -1
  14. package/lib/index.d.ts +1 -0
  15. package/lib/index.js +1 -0
  16. package/lib/index.js.map +1 -1
  17. package/lib/jsonTypeResolution/TypeResolver.d.ts +0 -1
  18. package/lib/jsonTypeResolution/TypeResolver.js +0 -3
  19. package/lib/jsonTypeResolution/TypeResolver.js.map +1 -1
  20. package/lib/jsonTypeResolution/TypeResolver.spec.js +10 -0
  21. package/lib/jsonTypeResolution/TypeResolver.spec.js.map +1 -1
  22. package/lib/manifest/v1/DxComponentInputSchema.spec.js +18 -0
  23. package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -1
  24. package/lib/manifest/v1/DxContentMetaSchema.json +24 -32
  25. package/lib/manifest/v1/JobV1.d.ts +23 -18
  26. package/lib/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
  27. package/lib/manifest/v1/v1.d.ts +23 -18
  28. package/lib/manifest/v1/v1.spec.js +3 -66
  29. package/lib/manifest/v1/v1.spec.js.map +1 -1
  30. package/lib/processValidationResult.d.ts +2 -0
  31. package/lib/processValidationResult.js +24 -0
  32. package/lib/processValidationResult.js.map +1 -0
  33. package/lib/validators/customKeywordValidators.d.ts +2 -0
  34. package/lib/validators/customKeywordValidators.js +47 -0
  35. package/lib/validators/customKeywordValidators.js.map +1 -0
  36. package/lib/validators/customKeywordValidators.spec.d.ts +1 -0
  37. package/lib/validators/customKeywordValidators.spec.js +100 -0
  38. package/lib/validators/customKeywordValidators.spec.js.map +1 -0
  39. package/package.json +1 -1
  40. package/src/JsonSchemaService.spec.ts +2198 -0
  41. package/src/JsonSchemaService.ts +159 -0
  42. package/src/JsonValidationService.spec.ts +26 -2195
  43. package/src/JsonValidationService.ts +64 -226
  44. package/src/index.ts +1 -0
  45. package/src/jsonTypeResolution/TypeResolver.spec.ts +12 -0
  46. package/src/jsonTypeResolution/TypeResolver.ts +0 -4
  47. package/src/manifest/v1/DxComponentInputSchema.spec.ts +21 -0
  48. package/src/manifest/v1/DxContentMetaSchema.json +24 -32
  49. package/src/manifest/v1/JobV1.ts +23 -23
  50. package/src/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
  51. package/src/manifest/v1/v1.spec.ts +3 -73
  52. package/src/manifest/v1/v1.ts +23 -23
  53. package/src/processValidationResult.ts +20 -0
  54. package/src/validators/customKeywordValidators.spec.ts +171 -0
  55. package/src/validators/customKeywordValidators.ts +53 -0
  56. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,2198 @@
1
+ import { ComponentInputMetaSchema, JSONSchemaService } from './JsonSchemaService';
2
+ import { SchemaValidationError } from './errors/SchemaValidationError';
3
+
4
+ import { FormattedText } from './formatted-text/v1/formattedText';
5
+ import { JSONSchema } from '@squiz/json-schema-library';
6
+ import {
7
+ AnyPrimitiveType,
8
+ AnyResolvableType,
9
+ PrimitiveType,
10
+ ResolvableType,
11
+ TypeResolver,
12
+ } from './jsonTypeResolution/TypeResolver';
13
+ import { FormattedTextType, SquizImageType, SquizLinkType } from './primitiveTypes';
14
+ import { TypeResolverBuilder } from './jsonTypeResolution/TypeResolverBuilder';
15
+ import { MatrixAssetUri } from './validators/utils/matrixAssetValidator';
16
+ import { MatrixAssetLinkType, MatrixAssetType } from './resolvableTypes';
17
+
18
+ function expectToThrowErrorMatchingTypeAndMessage(
19
+ // eslint-disable-next-line @typescript-eslint/ban-types
20
+ received: Function,
21
+ // eslint-disable-next-line @typescript-eslint/ban-types
22
+ errorType: Function,
23
+ message: string,
24
+ validationExpected?: any,
25
+ ) {
26
+ let error: null | SchemaValidationError = null;
27
+
28
+ try {
29
+ received();
30
+ } catch (e: any) {
31
+ error = e;
32
+ }
33
+
34
+ expect(error).toBeDefined();
35
+ expect(error?.message).toEqual(message);
36
+ expect(error).toBeInstanceOf(errorType);
37
+ expect(error?.validationData).toEqual(validationExpected);
38
+ }
39
+
40
+ const defaultSchema: JSONSchema = {
41
+ type: 'object',
42
+ properties: {
43
+ myProperty: {
44
+ type: 'string',
45
+ },
46
+ },
47
+ required: ['myProperty'],
48
+ };
49
+ function primitiveTypeFixture<T extends string>(title: T, schema: JSONSchema = defaultSchema) {
50
+ return PrimitiveType({
51
+ ...schema,
52
+ title,
53
+ });
54
+ }
55
+
56
+ function resolvableTypeFixture<T extends string>(title: T, schema: JSONSchema = defaultSchema) {
57
+ return ResolvableType({
58
+ ...schema,
59
+ title,
60
+ });
61
+ }
62
+
63
+ describe('JsonSchemaService', () => {
64
+ let jsonSchemaService: JSONSchemaService<AnyPrimitiveType, AnyResolvableType>;
65
+ beforeAll(() => {
66
+ const typeResolver = new TypeResolver([FormattedTextType]);
67
+ jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
68
+ });
69
+ describe('validateContentSchema', () => {
70
+ it('should return true for a valid content schema', () => {
71
+ const contentSchema = {
72
+ type: 'object',
73
+
74
+ properties: {
75
+ 'my-input': { type: 'number' },
76
+ },
77
+
78
+ required: ['my-input'],
79
+ };
80
+ const result = jsonSchemaService.validateInput(contentSchema);
81
+ expect(result).toBe(true);
82
+ });
83
+
84
+ it('should return false for an invalid content schema, where the required property is missed from a child object', () => {
85
+ const contentSchema = {
86
+ type: 'object',
87
+
88
+ properties: {
89
+ 'my-input': {
90
+ type: 'object',
91
+
92
+ properties: {
93
+ 'my-deep-input': { type: 'object' },
94
+ },
95
+ },
96
+ },
97
+
98
+ required: ['my-input'],
99
+ };
100
+ expectToThrowErrorMatchingTypeAndMessage(
101
+ () => {
102
+ jsonSchemaService.validateInput(contentSchema);
103
+ },
104
+ SchemaValidationError,
105
+ 'failed validation: The required property `required` is missing at `#/properties/my-input`',
106
+ {
107
+ '#/properties/my-input': [
108
+ {
109
+ data: { key: 'required', pointer: '#/properties/my-input' },
110
+ message: 'The required property `required` is missing at `#/properties/my-input`',
111
+ },
112
+ ],
113
+ },
114
+ );
115
+ });
116
+
117
+ it('should throw a SchemaValidationError for an invalid content schema, top level type must be object', () => {
118
+ const contentSchema = {
119
+ type: 'array',
120
+
121
+ items: {
122
+ type: 'object',
123
+ properties: {
124
+ 'my-input': { type: 'number' },
125
+ },
126
+ required: ['my-input'],
127
+ },
128
+ };
129
+ expectToThrowErrorMatchingTypeAndMessage(
130
+ () => {
131
+ jsonSchemaService.validateInput(contentSchema);
132
+ },
133
+ SchemaValidationError,
134
+ `failed validation: Expected value at \`#/type\` to be \`object\`, but value given is \`array\``,
135
+ {
136
+ '#/type': [
137
+ {
138
+ data: { expected: 'object', pointer: '#/type', value: 'array' },
139
+ message: 'Expected value at `#/type` to be `object`, but value given is `array`',
140
+ },
141
+ ],
142
+ },
143
+ );
144
+ });
145
+
146
+ it('should throw a SchemaValidationError for an invalid content schema missing the required property', () => {
147
+ const contentSchema = {
148
+ type: 'object',
149
+
150
+ properties: {
151
+ 'my-input': { type: 'number' },
152
+ },
153
+ };
154
+ expectToThrowErrorMatchingTypeAndMessage(
155
+ () => {
156
+ jsonSchemaService.validateInput(contentSchema);
157
+ },
158
+ SchemaValidationError,
159
+ 'failed validation: The required property `required` is missing at `#`',
160
+ {
161
+ '#': [
162
+ { data: { key: 'required', pointer: '#' }, message: 'The required property `required` is missing at `#`' },
163
+ ],
164
+ },
165
+ );
166
+ });
167
+ });
168
+
169
+ describe('SquizImage input', () => {
170
+ it('should validate a squizImage input', () => {
171
+ const schema = {
172
+ type: 'object',
173
+ properties: {
174
+ image: {
175
+ type: 'SquizImage',
176
+ },
177
+ },
178
+ };
179
+
180
+ const value: SquizImageType['__shape__'] = {
181
+ name: 'My Image',
182
+ alt: 'My Image that did not load',
183
+ caption: 'This above is a loaded image',
184
+ imageVariations: {
185
+ original: {
186
+ url: 'https://picsum.photos/200/300',
187
+ width: 100,
188
+ height: 100,
189
+ byteSize: 1000,
190
+ mimeType: 'image/jpeg',
191
+ aspectRatio: '1:1',
192
+ sha1Hash: '1234567890abcdef1234567890abcdef12345678',
193
+ },
194
+ },
195
+ };
196
+ expect(jsonSchemaService.validateInput(value, schema)).toEqual(true);
197
+ });
198
+
199
+ it('should error if SquizImage type is missing required properties', async () => {
200
+ const schema = {
201
+ type: 'object',
202
+ properties: {
203
+ myProp: {
204
+ type: 'SquizImage',
205
+ },
206
+ },
207
+ };
208
+
209
+ const value: { myProp: SquizImageType['__shape__'] } = {
210
+ // @ts-expect-error - missing required properties
211
+ myProp: {
212
+ alt: 'alt',
213
+ caption: 'caption',
214
+ name: 'name',
215
+ },
216
+ };
217
+ expectToThrowErrorMatchingTypeAndMessage(
218
+ () => {
219
+ jsonSchemaService.validateInput(value, schema);
220
+ },
221
+ SchemaValidationError,
222
+ 'failed validation: Expected `Object {"alt":"alt","caption":"caption","name":"name"}` (object) in `#/myProp` to be of type `SquizImage`',
223
+ {
224
+ '#/myProp': [
225
+ {
226
+ data: {
227
+ expected: 'SquizImage',
228
+ pointer: '#/myProp',
229
+ received: 'object',
230
+ value: { alt: 'alt', caption: 'caption', name: 'name' },
231
+ },
232
+ message:
233
+ 'Expected `Object {"alt":"alt","caption":"caption","name":"name"}` (object) in `#/myProp` to be of type `SquizImage`',
234
+ },
235
+ ],
236
+ },
237
+ );
238
+ });
239
+ });
240
+
241
+ describe('FormattedText input', () => {
242
+ it('should handle type as an array', () => {
243
+ const functionInputSchema = {
244
+ type: 'object',
245
+
246
+ properties: {
247
+ 'my-input': { type: ['number', 'string'] },
248
+ },
249
+
250
+ required: ['my-input'],
251
+ };
252
+
253
+ expect(
254
+ jsonSchemaService.validateInput(
255
+ {
256
+ 'my-input': 'formattedText',
257
+ },
258
+ functionInputSchema,
259
+ ),
260
+ ).toEqual(true);
261
+
262
+ expect(
263
+ jsonSchemaService.validateInput(
264
+ {
265
+ 'my-input': 123,
266
+ },
267
+ functionInputSchema,
268
+ ),
269
+ ).toEqual(true);
270
+ });
271
+
272
+ it.failing('should handle type is an array of both string and FormattedText', () => {
273
+ const formattedText: FormattedText = [
274
+ {
275
+ tag: 'p',
276
+ type: 'tag',
277
+ children: [
278
+ { type: 'text', value: 'This is some ' },
279
+ { type: 'text', value: 'Link to asset 12345' },
280
+ { type: 'text', value: ' with an image ' },
281
+ { type: 'text', value: '.' },
282
+ ],
283
+ },
284
+ ];
285
+ const functionInputSchema = {
286
+ type: 'object',
287
+
288
+ properties: {
289
+ 'my-input': { type: ['FormattedText', 'string'] },
290
+ },
291
+
292
+ required: ['my-input'],
293
+ };
294
+
295
+ expect(
296
+ jsonSchemaService.validateInput(
297
+ {
298
+ 'my-input': formattedText,
299
+ },
300
+ functionInputSchema,
301
+ ),
302
+ ).toEqual(true);
303
+
304
+ expect(
305
+ jsonSchemaService.validateInput(
306
+ {
307
+ 'my-input': 'hello',
308
+ },
309
+ functionInputSchema,
310
+ ),
311
+ ).toEqual(true);
312
+ });
313
+
314
+ it('should return true if input is formatted text', () => {
315
+ const functionInputSchema = {
316
+ type: 'object',
317
+
318
+ properties: {
319
+ 'my-input': { type: 'FormattedText' },
320
+ },
321
+
322
+ required: ['my-input'],
323
+ };
324
+
325
+ const formattedText: FormattedText = [
326
+ {
327
+ tag: 'p',
328
+ type: 'tag',
329
+ children: [
330
+ { type: 'text', value: 'This is some ' },
331
+ { type: 'text', value: 'Link to asset 12345' },
332
+ { type: 'text', value: ' with an image ' },
333
+ { type: 'text', value: '.' },
334
+ ],
335
+ },
336
+ ];
337
+ const inputValue = {
338
+ 'my-input': formattedText,
339
+ };
340
+
341
+ expect(() => jsonSchemaService.validateInput(inputValue, functionInputSchema)).not.toThrow();
342
+ });
343
+
344
+ it('should throw an error if the FormattedText input is not formatted text', () => {
345
+ const functionInputSchema = {
346
+ type: 'object',
347
+
348
+ properties: {
349
+ 'my-input': { type: 'FormattedText' },
350
+ },
351
+
352
+ required: ['my-input'],
353
+ };
354
+ const inputValue = {
355
+ 'my-input': 123,
356
+ };
357
+ expectToThrowErrorMatchingTypeAndMessage(
358
+ () => {
359
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
360
+ },
361
+ SchemaValidationError,
362
+ 'failed validation: Value `123` in `#/my-input` does not match any given oneof schema',
363
+ {
364
+ '#/my-input': [
365
+ {
366
+ data: {
367
+ errors: [
368
+ {
369
+ code: 'type-error',
370
+ data: { expected: 'array', pointer: '#/my-input', received: 'number', value: 123 },
371
+ message: 'Expected `123` (number) in `#/my-input` to be of type `array`',
372
+ name: 'TypeError',
373
+ type: 'error',
374
+ },
375
+ ],
376
+ oneOf: [{ $ref: 'FormattedText.json' }],
377
+ pointer: '#/my-input',
378
+ value: '123',
379
+ },
380
+ message: 'Value `123` in `#/my-input` does not match any given oneof schema',
381
+ },
382
+ ],
383
+ },
384
+ );
385
+ });
386
+
387
+ it('should throw an error if the FormattedText input is invalid formatted text', () => {
388
+ const functionInputSchema = {
389
+ type: 'object',
390
+
391
+ properties: {
392
+ 'my-input': { type: 'FormattedText' },
393
+ },
394
+
395
+ required: ['my-input'],
396
+ };
397
+ const inputValue = {
398
+ 'my-input': {
399
+ something: 'aa',
400
+ },
401
+ };
402
+ expectToThrowErrorMatchingTypeAndMessage(
403
+ () => {
404
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
405
+ },
406
+ SchemaValidationError,
407
+ 'failed validation: Value `{"something":"aa"}` in `#/my-input` does not match any given oneof schema',
408
+ {
409
+ '#/my-input': [
410
+ {
411
+ data: {
412
+ errors: [
413
+ {
414
+ code: 'type-error',
415
+ data: { expected: 'array', pointer: '#/my-input', received: 'object', value: { something: 'aa' } },
416
+ message: 'Expected `Object {"something":"aa"}` (object) in `#/my-input` to be of type `array`',
417
+ name: 'TypeError',
418
+ type: 'error',
419
+ },
420
+ ],
421
+ oneOf: [{ $ref: 'FormattedText.json' }],
422
+ pointer: '#/my-input',
423
+ value: '{"something":"aa"}',
424
+ },
425
+ message: 'Value `{"something":"aa"}` in `#/my-input` does not match any given oneof schema',
426
+ },
427
+ ],
428
+ },
429
+ );
430
+ });
431
+
432
+ it('should throw an error if the FormattedText input is invalid formatted text (deeply nested)', () => {
433
+ const functionInputSchema = {
434
+ type: 'object',
435
+
436
+ properties: {
437
+ 'my-input': { type: 'FormattedText' },
438
+ },
439
+
440
+ required: ['my-input'],
441
+ };
442
+ const formattedText: FormattedText = [
443
+ {
444
+ tag: 'p',
445
+ type: 'tag',
446
+ children: [
447
+ { type: 'text', value: 'This is some ' },
448
+ { type: 'text', value: 'Link to asset 12345' },
449
+ { type: 'text', value: ' with an image ' },
450
+ { type: 'text', value: 123 as any }, // see here
451
+ { type: 'text', value: '.' },
452
+ ],
453
+ },
454
+ ];
455
+
456
+ const inputValue = {
457
+ 'my-input': formattedText,
458
+ };
459
+ expectToThrowErrorMatchingTypeAndMessage(
460
+ () => {
461
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
462
+ },
463
+ SchemaValidationError,
464
+ 'failed validation: Value `[{"tag":"p","type":"tag","children":[{"type":"text","value":"This is some "},{"type":"text","value":"Link to asset 12345"},{"type":"text","value":" with an image "},{"type":"text","value":123},{"type":"text","value":"."}]}]` in `#/my-input` does not match any given oneof schema',
465
+ {
466
+ '#/my-input': [
467
+ {
468
+ data: {
469
+ errors: [
470
+ {
471
+ code: 'any-of-error',
472
+ data: {
473
+ anyOf: [
474
+ { $ref: '#/definitions/HigherOrderFormattedNodes' },
475
+ { $ref: '#/definitions/BaseFormattedNodes' },
476
+ ],
477
+ pointer: '#/my-input/0',
478
+ value: {
479
+ children: [
480
+ { type: 'text', value: 'This is some ' },
481
+ { type: 'text', value: 'Link to asset 12345' },
482
+ { type: 'text', value: ' with an image ' },
483
+ { type: 'text', value: 123 },
484
+ { type: 'text', value: '.' },
485
+ ],
486
+ tag: 'p',
487
+ type: 'tag',
488
+ },
489
+ },
490
+ message: 'Object at `#/my-input/0` does not match any schema',
491
+ name: 'AnyOfError',
492
+ type: 'error',
493
+ },
494
+ ],
495
+ oneOf: [{ $ref: 'FormattedText.json' }],
496
+ pointer: '#/my-input',
497
+ value:
498
+ '[{"tag":"p","type":"tag","children":[{"type":"text","value":"This is some "},{"type":"text","value":"Link to asset 12345"},{"type":"text","value":" with an image "},{"type":"text","value":123},{"type":"text","value":"."}]}]',
499
+ },
500
+ message:
501
+ 'Value `[{"tag":"p","type":"tag","children":[{"type":"text","value":"This is some "},{"type":"text","value":"Link to asset 12345"},{"type":"text","value":" with an image "},{"type":"text","value":123},{"type":"text","value":"."}]}]` in `#/my-input` does not match any given oneof schema',
502
+ },
503
+ ],
504
+ },
505
+ );
506
+ });
507
+
508
+ it('should validate an array of formatted texts', () => {
509
+ const formattedText: FormattedText = [
510
+ {
511
+ tag: 'p',
512
+ type: 'tag',
513
+ children: [{ type: 'text', value: 'hello' }],
514
+ },
515
+ ];
516
+
517
+ const schema = {
518
+ type: 'object',
519
+ properties: {
520
+ arr: {
521
+ type: 'array',
522
+ items: { type: 'FormattedText' },
523
+ },
524
+ },
525
+ };
526
+ const value = {
527
+ arr: [formattedText],
528
+ };
529
+
530
+ expect(jsonSchemaService.validateInput(value, schema)).toEqual(true);
531
+ });
532
+
533
+ it('should throw an error if the FormattedText input is invalid formatted text (deeply, deeply nested)', () => {
534
+ const formattedText: FormattedText = [
535
+ {
536
+ tag: 'p',
537
+ type: 'tag',
538
+ children: [{ type: 'text', value: 'hello' }],
539
+ },
540
+ ];
541
+
542
+ const inputValue: any = {
543
+ 'my-input': formattedText,
544
+ deep: {
545
+ arr: [
546
+ {
547
+ prop: formattedText,
548
+ formattedTextArray: [formattedText, formattedText, { bad: 'data' }],
549
+ },
550
+ ],
551
+ },
552
+ };
553
+
554
+ const functionInputSchema = {
555
+ type: 'object',
556
+
557
+ properties: {
558
+ 'my-input': { type: 'FormattedText' },
559
+ deep: {
560
+ type: 'object',
561
+ properties: {
562
+ arr: {
563
+ type: 'array',
564
+ items: {
565
+ type: 'object',
566
+ required: ['prop', 'formattedTextArray'],
567
+ properties: {
568
+ prop: 'FormattedText',
569
+ formattedTextArray: {
570
+ type: 'array',
571
+ items: {
572
+ type: 'FormattedText',
573
+ },
574
+ },
575
+ },
576
+ },
577
+ },
578
+ },
579
+ required: ['arr'],
580
+ },
581
+ },
582
+
583
+ required: ['my-input', 'deep'],
584
+ };
585
+
586
+ expectToThrowErrorMatchingTypeAndMessage(
587
+ () => {
588
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
589
+ },
590
+ SchemaValidationError,
591
+ 'failed validation: Value `{"bad":"data"}` in `#/deep/arr/0/formattedTextArray/2` does not match any given oneof schema',
592
+ {
593
+ '#/deep/arr/0/formattedTextArray/2': [
594
+ {
595
+ data: {
596
+ errors: [
597
+ {
598
+ code: 'type-error',
599
+ data: {
600
+ expected: 'array',
601
+ pointer: '#/deep/arr/0/formattedTextArray/2',
602
+ received: 'object',
603
+ value: { bad: 'data' },
604
+ },
605
+ message:
606
+ 'Expected `Object {"bad":"data"}` (object) in `#/deep/arr/0/formattedTextArray/2` to be of type `array`',
607
+ name: 'TypeError',
608
+ type: 'error',
609
+ },
610
+ ],
611
+ oneOf: [{ $ref: 'FormattedText.json' }],
612
+ pointer: '#/deep/arr/0/formattedTextArray/2',
613
+ value: '{"bad":"data"}',
614
+ },
615
+ message:
616
+ 'Value `{"bad":"data"}` in `#/deep/arr/0/formattedTextArray/2` does not match any given oneof schema',
617
+ },
618
+ ],
619
+ },
620
+ );
621
+ });
622
+
623
+ it('should validate a FormattedText value when the schema is a $ref', async () => {
624
+ const formattedText: FormattedText = [
625
+ {
626
+ tag: 'p',
627
+ type: 'tag',
628
+ children: [{ type: 'text', value: 'hello' }],
629
+ },
630
+ ];
631
+
632
+ const schema = {
633
+ type: 'object',
634
+ properties: {
635
+ 'my-input': { $ref: '#/definitions/FormattedText' },
636
+ },
637
+ definitions: {
638
+ FormattedText: { type: 'FormattedText' },
639
+ },
640
+ required: ['my-input'],
641
+ };
642
+ const value = {
643
+ 'my-input': formattedText,
644
+ };
645
+
646
+ expect(jsonSchemaService.validateInput(value, schema)).toEqual(true);
647
+ });
648
+
649
+ it('should error when a FormattedText value is invalid when the schema is a $ref', async () => {
650
+ const schema = {
651
+ type: 'object',
652
+ properties: {
653
+ 'my-input': { $ref: '#/definitions/FormattedText' },
654
+ },
655
+ definitions: {
656
+ FormattedText: { type: 'FormattedText' },
657
+ },
658
+ required: ['my-input'],
659
+ };
660
+ const value = {
661
+ 'my-input': { bad: 'data' },
662
+ };
663
+
664
+ expectToThrowErrorMatchingTypeAndMessage(
665
+ () => {
666
+ jsonSchemaService.validateInput(value, schema);
667
+ },
668
+ SchemaValidationError,
669
+ 'failed validation: Value `{"bad":"data"}` in `#/my-input` does not match any given oneof schema',
670
+ {
671
+ '#/my-input': [
672
+ {
673
+ data: {
674
+ errors: [
675
+ {
676
+ code: 'type-error',
677
+ data: { expected: 'array', pointer: '#/my-input', received: 'object', value: { bad: 'data' } },
678
+ message: 'Expected `Object {"bad":"data"}` (object) in `#/my-input` to be of type `array`',
679
+ name: 'TypeError',
680
+ type: 'error',
681
+ },
682
+ ],
683
+ oneOf: [{ $ref: 'FormattedText.json' }],
684
+ pointer: '#/my-input',
685
+ value: '{"bad":"data"}',
686
+ },
687
+ message: 'Value `{"bad":"data"}` in `#/my-input` does not match any given oneof schema',
688
+ },
689
+ ],
690
+ },
691
+ );
692
+ });
693
+
694
+ it('should validate a FormattedText value when the schema is a $ref to a $ref', async () => {
695
+ const formattedText: FormattedText = [
696
+ {
697
+ tag: 'p',
698
+ type: 'tag',
699
+ children: [{ type: 'text', value: 'hello' }],
700
+ },
701
+ ];
702
+
703
+ const schema = {
704
+ type: 'object',
705
+ properties: {
706
+ 'my-input': { $ref: '#/definitions/FormattedText' },
707
+ },
708
+ definitions: {
709
+ FormattedText: { $ref: '#/definitions/FormattedText2' },
710
+ FormattedText2: { type: 'FormattedText' },
711
+ },
712
+ required: ['my-input'],
713
+ };
714
+ const value = {
715
+ 'my-input': formattedText,
716
+ };
717
+
718
+ expect(jsonSchemaService.validateInput(value, schema)).toEqual(true);
719
+ });
720
+
721
+ it('should validate a FormattedText value when the schema is in an if/then/else', async () => {
722
+ const formattedText: FormattedText = [
723
+ {
724
+ tag: 'p',
725
+ type: 'tag',
726
+ children: [{ type: 'text', value: 'hello' }],
727
+ },
728
+ ];
729
+
730
+ const schema = {
731
+ type: 'object',
732
+ properties: {
733
+ 'my-input': {
734
+ if: { type: 'string' },
735
+ then: { type: 'string' },
736
+ else: { type: 'FormattedText' },
737
+ },
738
+ },
739
+ required: ['my-input'],
740
+ };
741
+ const value = {
742
+ 'my-input': formattedText,
743
+ };
744
+
745
+ expect(jsonSchemaService.validateInput(value, schema)).toEqual(true);
746
+ });
747
+
748
+ it('should allow an empty array', async () => {
749
+ const schema = {
750
+ type: 'object',
751
+ properties: {
752
+ 'my-input': { type: 'FormattedText' },
753
+ },
754
+ required: [],
755
+ };
756
+
757
+ expect(jsonSchemaService.validateInput({ 'my-input': [] }, schema)).toEqual(true);
758
+ });
759
+
760
+ it.each([
761
+ ['with attributes', { title: 'Link title' }],
762
+ ['without attributes', undefined],
763
+ ])('should validate link-to-matrix-asset - %s', (_: string, attributes?: Record<string, string>) => {
764
+ const formattedText: FormattedText = [
765
+ {
766
+ type: 'link-to-matrix-asset',
767
+ matrixIdentifier: 'matrix-identifier',
768
+ matrixDomain: 'https://matrix-api-url',
769
+ target: '_blank',
770
+ matrixAssetId: '12345',
771
+ children: [{ type: 'text', value: 'hello' }],
772
+ attributes,
773
+ },
774
+ ];
775
+ const schema = {
776
+ type: 'object',
777
+ properties: {
778
+ myInput: {
779
+ type: 'FormattedText',
780
+ },
781
+ },
782
+ };
783
+ const value = {
784
+ myInput: formattedText,
785
+ };
786
+
787
+ expect(jsonSchemaService.validateInput(value, schema)).toBe(true);
788
+ });
789
+
790
+ it('should throw if link-to-matrix-asset contains a reserved attribute', () => {
791
+ const formattedText: FormattedText = [
792
+ {
793
+ type: 'link-to-matrix-asset',
794
+ matrixIdentifier: 'matrix-identifier',
795
+ matrixDomain: 'https://matrix-api-url',
796
+ target: '_blank',
797
+ matrixAssetId: '12345',
798
+ children: [{ type: 'text', value: 'hello' }],
799
+ attributes: {
800
+ // href is reserved (resolved from the selected Matrix asset)
801
+ href: 'https://www.my-matrix.squiz.net',
802
+ } as any,
803
+ },
804
+ ];
805
+ const schema = {
806
+ type: 'object',
807
+ properties: {
808
+ myInput: {
809
+ type: 'FormattedText',
810
+ },
811
+ },
812
+ };
813
+ const value = {
814
+ myInput: formattedText,
815
+ };
816
+
817
+ expect(() => jsonSchemaService.validateInput(value, schema)).toThrow(/does not match any given oneof schema/);
818
+ });
819
+
820
+ it.each([
821
+ ['with attributes', { title: 'Link title' }],
822
+ ['without attributes', undefined],
823
+ ])('should validate matrix-image - %s', (_: string, attributes?: Record<string, string>) => {
824
+ const formattedText: FormattedText = [
825
+ {
826
+ type: 'matrix-image',
827
+ matrixIdentifier: 'matrix-identifier',
828
+ matrixDomain: 'https://matrix-api-url',
829
+ matrixAssetId: '12345',
830
+ attributes,
831
+ },
832
+ ];
833
+ const schema = {
834
+ type: 'object',
835
+ properties: {
836
+ myInput: {
837
+ type: 'FormattedText',
838
+ },
839
+ },
840
+ };
841
+ const value = {
842
+ myInput: formattedText,
843
+ };
844
+
845
+ expect(jsonSchemaService.validateInput(value, schema)).toBe(true);
846
+ });
847
+
848
+ it('should throw if matrix-image contains a reserved attribute', () => {
849
+ const formattedText: FormattedText = [
850
+ {
851
+ type: 'matrix-image',
852
+ matrixIdentifier: 'matrix-identifier',
853
+ matrixDomain: 'https://matrix-api-url',
854
+ matrixAssetId: '12345',
855
+ attributes: {
856
+ // src is reserved (resolved from the selected Matrix asset)
857
+ src: 'https://www.my-matrix.squiz.net/image.png',
858
+ } as any,
859
+ },
860
+ ];
861
+ const schema = {
862
+ type: 'object',
863
+ properties: {
864
+ myInput: {
865
+ type: 'FormattedText',
866
+ },
867
+ },
868
+ };
869
+ const value = {
870
+ myInput: formattedText,
871
+ };
872
+
873
+ expect(() => jsonSchemaService.validateInput(value, schema)).toThrow(/does not match any given oneof schema/);
874
+ });
875
+
876
+ it.each([
877
+ ['with attributes', { title: 'Link title' }],
878
+ ['without attributes', undefined],
879
+ ])('should validate dam-image - %s', (_: string, attributes?: Record<string, string>) => {
880
+ const formattedText: FormattedText = [
881
+ {
882
+ type: 'dam-image',
883
+ damSystemIdentifier: 'dam-identifier',
884
+ damObjectId: '12345',
885
+ damSystemType: 'bynder',
886
+ attributes,
887
+ },
888
+ ];
889
+ const schema = {
890
+ type: 'object',
891
+ properties: {
892
+ myInput: {
893
+ type: 'FormattedText',
894
+ },
895
+ },
896
+ };
897
+ const value = {
898
+ myInput: formattedText,
899
+ };
900
+
901
+ expect(jsonSchemaService.validateInput(value, schema)).toBe(true);
902
+ });
903
+
904
+ it('should throw if dam-image contains a reserved attribute', () => {
905
+ const formattedText: FormattedText = [
906
+ {
907
+ type: 'dam-image',
908
+ damSystemIdentifier: 'dam-identifier',
909
+ damObjectId: '12345',
910
+ damSystemType: 'bynder',
911
+ attributes: {
912
+ // src is reserved (resolved from the selected DAM asset)
913
+ src: 'https://www.my-matrix.squiz.net/image.png',
914
+ } as any,
915
+ },
916
+ ];
917
+ const schema = {
918
+ type: 'object',
919
+ properties: {
920
+ myInput: {
921
+ type: 'FormattedText',
922
+ },
923
+ },
924
+ };
925
+ const value = {
926
+ myInput: formattedText,
927
+ };
928
+
929
+ expect(() => jsonSchemaService.validateInput(value, schema)).toThrow(/does not match any given oneof schema/);
930
+ });
931
+ });
932
+
933
+ describe('standard inputs', () => {
934
+ const functionInputSchema = {
935
+ type: 'object',
936
+
937
+ properties: {
938
+ 'my-input': { type: 'number' },
939
+ },
940
+
941
+ required: ['my-input'],
942
+ };
943
+
944
+ it('should return true for valid input values', () => {
945
+ const inputValue = {
946
+ 'my-input': 123,
947
+ };
948
+ const result = jsonSchemaService.validateInput(inputValue, functionInputSchema);
949
+ expect(result).toBe(true);
950
+ });
951
+
952
+ it('should throw a SchemaValidationError for invalid input type', () => {
953
+ const inputValue = {
954
+ 'my-input': '123',
955
+ };
956
+ expectToThrowErrorMatchingTypeAndMessage(
957
+ () => {
958
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
959
+ },
960
+ SchemaValidationError,
961
+ `failed validation: Expected \`123\` (string) in \`#/my-input\` to be of type \`number\``,
962
+ {
963
+ '#/my-input': [
964
+ {
965
+ data: { expected: 'number', pointer: '#/my-input', received: 'string', value: '123' },
966
+ message: 'Expected `123` (string) in `#/my-input` to be of type `number`',
967
+ },
968
+ ],
969
+ },
970
+ );
971
+ });
972
+
973
+ it('should throw a SchemaValidationError for invalid input missing properties', () => {
974
+ const inputValue = {};
975
+ expectToThrowErrorMatchingTypeAndMessage(
976
+ () => {
977
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
978
+ },
979
+ SchemaValidationError,
980
+ `failed validation: The required property \`my-input\` is missing at \`#\``,
981
+ {
982
+ '#': [
983
+ { data: { key: 'my-input', pointer: '#' }, message: 'The required property `my-input` is missing at `#`' },
984
+ ],
985
+ },
986
+ );
987
+ });
988
+
989
+ it('should throw a SchemaValidationError with a truncated enum value list if there are more than 5 enum options', () => {
990
+ expectToThrowErrorMatchingTypeAndMessage(
991
+ () => {
992
+ jsonSchemaService.validateInput(
993
+ { enum: 'z' },
994
+ { type: 'object', properties: { enum: { type: 'string', enum: ['a', 'b', 'c', 'd', 'e', 'f', 'g'] } } },
995
+ );
996
+ },
997
+ SchemaValidationError,
998
+ 'failed validation: Expected given value `z` in #/enum` to be one of `[a, b, c, d, e, ... 2 more]`',
999
+ {
1000
+ '#/enum': [
1001
+ {
1002
+ data: { pointer: '#/enum', value: 'z', values: ['a', 'b', 'c', 'd', 'e', 'f', 'g'] },
1003
+ message: 'Expected given value `z` in #/enum` to be one of `[a, b, c, d, e, ... 2 more]`',
1004
+ },
1005
+ ],
1006
+ },
1007
+ );
1008
+ });
1009
+
1010
+ // TODO DEVX-658
1011
+ it.skip('should throw a SchemaValidationError for invalid input additional properties', () => {
1012
+ const inputValue = {
1013
+ 'my-input': 123,
1014
+ 'my-input-2': 123,
1015
+ };
1016
+ expect(() => {
1017
+ jsonSchemaService.validateInput(inputValue, functionInputSchema);
1018
+ }).toThrowErrorMatchingInlineSnapshot();
1019
+ });
1020
+ });
1021
+
1022
+ describe('validateInput', () => {
1023
+ it.each([String('123'), Number(123), [123]])(
1024
+ 'should validate any primitive type with its resolvable type %s',
1025
+ (propertyValue) => {
1026
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1027
+ const MyResolvableNumber = resolvableTypeFixture('MyResolvableNumber', { type: 'number' });
1028
+ const MyResolvableArray = resolvableTypeFixture('MyResolvableArray', { type: 'array' });
1029
+ const jsonSchemaService = new JSONSchemaService(
1030
+ new TypeResolver([primitiveSchema], [MyResolvableNumber, MyResolvableArray], {
1031
+ MyPrimitive: {
1032
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1033
+ MyResolvableNumber: (value: number) => value.toString(),
1034
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1035
+ MyResolvableArray: (value: any[]) => value.join(''),
1036
+ },
1037
+ }),
1038
+ ComponentInputMetaSchema,
1039
+ );
1040
+
1041
+ expect(
1042
+ jsonSchemaService.validateInput(
1043
+ { myProperty: propertyValue },
1044
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1045
+ ),
1046
+ ).toEqual(true);
1047
+ },
1048
+ );
1049
+
1050
+ it('should error when a primitive type is provided a value that cannot be resolved by its resolvable types', () => {
1051
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1052
+ const MyResolvableNumber = resolvableTypeFixture('MyResolvableNumber', { type: 'number' });
1053
+ const MyResolvableArray = resolvableTypeFixture('MyResolvableArray', { type: 'array' });
1054
+ const jsonSchemaService = new JSONSchemaService(
1055
+ new TypeResolver([primitiveSchema], [MyResolvableNumber, MyResolvableArray], {
1056
+ MyPrimitive: {
1057
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1058
+ MyResolvableNumber: (value: number) => value.toString(),
1059
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1060
+ MyResolvableArray: (value: any[]) => value.join(''),
1061
+ },
1062
+ }),
1063
+ ComponentInputMetaSchema,
1064
+ );
1065
+
1066
+ expect(() => {
1067
+ jsonSchemaService.validateInput(
1068
+ { myProperty: true },
1069
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1070
+ );
1071
+ }).toThrowError();
1072
+ });
1073
+
1074
+ it.each([String('123'), Number(123), [123]])(
1075
+ 'should validate a primitive type when defined as a ref with resolvable value %s',
1076
+ (propertyValue) => {
1077
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1078
+ const MyResolvableNumber = resolvableTypeFixture('MyResolvableNumber', { type: 'number' });
1079
+ const MyResolvableArray = resolvableTypeFixture('MyResolvableArray', { type: 'array' });
1080
+ const jsonSchemaService = new JSONSchemaService(
1081
+ new TypeResolver([primitiveSchema], [MyResolvableNumber, MyResolvableArray], {
1082
+ MyPrimitive: {
1083
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1084
+ MyResolvableNumber: (value: number) => value.toString(),
1085
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1086
+ MyResolvableArray: (value: any[]) => value.join(''),
1087
+ },
1088
+ }),
1089
+ ComponentInputMetaSchema,
1090
+ );
1091
+
1092
+ expect(
1093
+ jsonSchemaService.validateInput(
1094
+ { myProperty: propertyValue },
1095
+ {
1096
+ type: 'object',
1097
+ properties: { myProperty: { $ref: '#/definitions/Ref' } },
1098
+ definitions: { Ref: { type: 'MyPrimitive' } },
1099
+ },
1100
+ ),
1101
+ ).toEqual(true);
1102
+ },
1103
+ );
1104
+
1105
+ it('should not validate on a primitive type against a resolvable type when a resolver is not defined', () => {
1106
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1107
+ const MyResolvableNumber = resolvableTypeFixture('MyResolvableNumber', { type: 'number' });
1108
+ const MyResolvableArray = resolvableTypeFixture('MyResolvableArray', { type: 'array' });
1109
+ const jsonSchemaService = new JSONSchemaService(
1110
+ new TypeResolver([primitiveSchema], [MyResolvableNumber, MyResolvableArray], {
1111
+ MyPrimitive: {
1112
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1113
+ MyResolvableNumber: (value: number) => value.toString(),
1114
+ },
1115
+ }),
1116
+ ComponentInputMetaSchema,
1117
+ );
1118
+
1119
+ expect(() => {
1120
+ jsonSchemaService.validateInput(
1121
+ { myProperty: [123] },
1122
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1123
+ );
1124
+ }).toThrowError();
1125
+ });
1126
+
1127
+ it('should validate a primitive type against similar but different resolvable types', () => {
1128
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1129
+ const jsonSchemaService = new JSONSchemaService(
1130
+ new TypeResolver(
1131
+ [primitiveSchema],
1132
+ [
1133
+ resolvableTypeFixture('MyResolvableSrcNumber', {
1134
+ type: 'object',
1135
+ properties: {
1136
+ src: { type: 'number' },
1137
+ },
1138
+ }),
1139
+ resolvableTypeFixture('MyResolvableSrcString', {
1140
+ type: 'object',
1141
+ properties: {
1142
+ src: { type: 'string' },
1143
+ },
1144
+ }),
1145
+ ],
1146
+ {
1147
+ MyPrimitive: {
1148
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1149
+ MyResolvableSrcNumber: (value: { src: number }) => value.src.toString(),
1150
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1151
+ MyResolvableSrcString: (value: { src: string }) => value.src,
1152
+ },
1153
+ },
1154
+ ),
1155
+ ComponentInputMetaSchema,
1156
+ );
1157
+
1158
+ expect(
1159
+ jsonSchemaService.validateInput(
1160
+ {
1161
+ myProperty: {
1162
+ src: 123,
1163
+ },
1164
+ },
1165
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1166
+ ),
1167
+ ).toEqual(true);
1168
+ expect(
1169
+ jsonSchemaService.validateInput(
1170
+ {
1171
+ myProperty: {
1172
+ src: '123',
1173
+ },
1174
+ },
1175
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1176
+ ),
1177
+ ).toEqual(true);
1178
+ });
1179
+ });
1180
+
1181
+ describe('resolveInput', () => {
1182
+ it.each([String('123'), Number(123), [123]])(
1183
+ 'should resolve a primitive type from its resolvable type %s',
1184
+ async (resolvableValue) => {
1185
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1186
+ const jsonSchemaService = new JSONSchemaService(
1187
+ new TypeResolver(
1188
+ [primitiveSchema],
1189
+ [
1190
+ resolvableTypeFixture('MyResolvableNumber', { type: 'number' }),
1191
+ resolvableTypeFixture('MyResolvableArray', { type: 'array' }),
1192
+ ],
1193
+ {
1194
+ MyPrimitive: {
1195
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1196
+ MyResolvableNumber: (value: number) => value.toString(),
1197
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1198
+ MyResolvableArray: (value: any[]) => value.join(''),
1199
+ },
1200
+ },
1201
+ ),
1202
+ ComponentInputMetaSchema,
1203
+ );
1204
+
1205
+ await expect(
1206
+ jsonSchemaService.resolveInput(
1207
+ { myProperty: resolvableValue },
1208
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1209
+ ),
1210
+ ).resolves.toEqual({ myProperty: '123' });
1211
+ },
1212
+ );
1213
+
1214
+ it.each([
1215
+ [{ src: 'MyString' }, 'MyString'],
1216
+ [{ src: 1132 }, '1132'],
1217
+ ])('should resolve a resolvable type %s against the correct resolver to %s', async (resolvableValue, output) => {
1218
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1219
+ const jsonSchemaService = new JSONSchemaService(
1220
+ new TypeResolver(
1221
+ [primitiveSchema],
1222
+ [
1223
+ resolvableTypeFixture('MyResolvableSrcString', {
1224
+ type: 'object',
1225
+ properties: { src: { type: 'string' } },
1226
+ }),
1227
+ resolvableTypeFixture('MyResolvableSrcNumber', {
1228
+ type: 'object',
1229
+ properties: { src: { type: 'number' } },
1230
+ }),
1231
+ ],
1232
+ {
1233
+ MyPrimitive: {
1234
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1235
+ MyResolvableSrcNumber: (value: { src: number }) => value.src.toString(),
1236
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1237
+ MyResolvableSrcString: (value: { src: string }) => value.src,
1238
+ },
1239
+ },
1240
+ ),
1241
+ ComponentInputMetaSchema,
1242
+ );
1243
+
1244
+ await expect(
1245
+ jsonSchemaService.resolveInput(
1246
+ { myProperty: resolvableValue },
1247
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' } } },
1248
+ ),
1249
+ ).resolves.toEqual({ myProperty: output });
1250
+ });
1251
+
1252
+ it('should resolve a primitive type from its resolvable type %s', async () => {
1253
+ const primitiveSchema = primitiveTypeFixture('MyPrimitive', { type: 'string' });
1254
+ const jsonSchemaService = new JSONSchemaService(
1255
+ new TypeResolver([primitiveSchema], [resolvableTypeFixture('MyResolvableWithError', { type: 'number' })], {
1256
+ MyPrimitive: {
1257
+ // @ts-expect-error - fixture is unknown but we know the actual shape
1258
+ MyResolvableWithError: (_value: number) => {
1259
+ throw new Error('Failed resolving!!');
1260
+ },
1261
+ },
1262
+ }),
1263
+ ComponentInputMetaSchema,
1264
+ );
1265
+
1266
+ await expect(
1267
+ jsonSchemaService.resolveInput(
1268
+ { myProperty: 123, myProperty2: 234 },
1269
+ { type: 'object', properties: { myProperty: { type: 'MyPrimitive' }, myProperty2: { type: 'MyPrimitive' } } },
1270
+ ),
1271
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1272
+ "Error(s) occurred when resolving JSON:
1273
+ Error: Error resolving JSON at #/myProperty: Failed resolving!!
1274
+ Error: Error resolving JSON at #/myProperty2: Failed resolving!!"
1275
+ `);
1276
+ });
1277
+
1278
+ it('should resolve a FormattedText empty list', async () => {
1279
+ const types = TypeResolverBuilder.new().addPrimitive(FormattedTextType).build();
1280
+ const jsonSchemaService = new JSONSchemaService(types, ComponentInputMetaSchema);
1281
+
1282
+ await expect(
1283
+ jsonSchemaService.resolveInput(
1284
+ { myProp: [] },
1285
+ { type: 'object', properties: { myProp: { type: 'FormattedText' } }, required: [] },
1286
+ ),
1287
+ ).resolves.toEqual({ myProp: [] });
1288
+ });
1289
+ });
1290
+ });
1291
+
1292
+ describe('JSONSchemaService - validation', () => {
1293
+ const typeResolver = TypeResolverBuilder.new()
1294
+ .addPrimitive(SquizImageType)
1295
+ .addPrimitive(SquizLinkType)
1296
+ .addPrimitive(FormattedTextType)
1297
+ .build();
1298
+
1299
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
1300
+
1301
+ it('should validate a schema with all the squiz primitive types', () => {
1302
+ const schema = {
1303
+ type: 'object',
1304
+ properties: {
1305
+ image: { type: 'SquizImage' },
1306
+ link: { type: 'SquizLink' },
1307
+ text: { type: 'FormattedText' },
1308
+ },
1309
+ required: ['image', 'link', 'text'],
1310
+ };
1311
+ const formattedText: FormattedText = [
1312
+ {
1313
+ tag: 'p',
1314
+ type: 'tag',
1315
+ children: [{ type: 'text', value: 'hello' }],
1316
+ },
1317
+ ];
1318
+ const input: {
1319
+ image: SquizImageType['__shape__'];
1320
+ link: SquizLinkType['__shape__'];
1321
+ text: FormattedText;
1322
+ } = {
1323
+ image: {
1324
+ name: 'test-image.jpeg',
1325
+ imageVariations: {
1326
+ original: {
1327
+ aspectRatio: '1:1',
1328
+ height: 100,
1329
+ width: 100,
1330
+ url: 'https://www.squiz.net',
1331
+ byteSize: 100,
1332
+ mimeType: 'image/jpeg',
1333
+ sha1Hash: '123',
1334
+ },
1335
+ },
1336
+ },
1337
+ link: {
1338
+ text: 'test-link',
1339
+ url: 'https://www.squiz.net',
1340
+ target: '_blank',
1341
+ },
1342
+ text: formattedText,
1343
+ };
1344
+
1345
+ const result = jsonSchemaService.validateInput(input, schema);
1346
+
1347
+ expect(result).toEqual(true);
1348
+ });
1349
+
1350
+ it('should validate a schema with all the squiz primitive types and matrix-asset-uri format', () => {
1351
+ const schema = {
1352
+ type: 'object',
1353
+ properties: {
1354
+ image: { type: 'SquizImage' },
1355
+ link: { type: 'SquizLink' },
1356
+ text: { type: 'FormattedText' },
1357
+ asset: {
1358
+ type: 'string',
1359
+ format: 'matrix-asset-uri',
1360
+ },
1361
+ },
1362
+ required: ['image', 'link', 'text', 'asset'],
1363
+ };
1364
+ const formattedText: FormattedText = [
1365
+ {
1366
+ tag: 'p',
1367
+ type: 'tag',
1368
+ children: [{ type: 'text', value: 'hello' }],
1369
+ },
1370
+ ];
1371
+ const input: {
1372
+ image: SquizImageType['__shape__'];
1373
+ link: SquizLinkType['__shape__'];
1374
+ text: FormattedText;
1375
+ asset: MatrixAssetUri;
1376
+ } = {
1377
+ image: {
1378
+ name: 'test-image.jpeg',
1379
+ imageVariations: {
1380
+ original: {
1381
+ aspectRatio: '1:1',
1382
+ height: 100,
1383
+ width: 100,
1384
+ url: 'https://www.squiz.net',
1385
+ byteSize: 100,
1386
+ mimeType: 'image/jpeg',
1387
+ sha1Hash: '123',
1388
+ },
1389
+ },
1390
+ },
1391
+ link: {
1392
+ text: 'test-link',
1393
+ url: 'https://www.squiz.net',
1394
+ target: '_blank',
1395
+ },
1396
+ text: formattedText,
1397
+ asset: 'matrix-asset://identifier/123',
1398
+ };
1399
+
1400
+ const result = jsonSchemaService.validateInput(input, schema);
1401
+
1402
+ expect(result).toEqual(true);
1403
+ });
1404
+
1405
+ it('should catch validation errors when there is a schema with all the squiz primitive types', () => {
1406
+ const schema = {
1407
+ type: 'object',
1408
+ properties: {
1409
+ image: { type: 'SquizImage' },
1410
+ link: { type: 'SquizLink' },
1411
+ text: { type: 'FormattedText' },
1412
+ },
1413
+ required: ['image', 'link', 'text'],
1414
+ };
1415
+ const formattedText: FormattedText = [
1416
+ //@ts-expect-error - wrong type
1417
+ {
1418
+ children: [{ type: 'text', value: 'hello' }],
1419
+ },
1420
+ ];
1421
+ const input: {
1422
+ image: SquizImageType['__shape__'];
1423
+ link: SquizLinkType['__shape__'];
1424
+ text: FormattedText;
1425
+ } = {
1426
+ image: {
1427
+ name: 'test-image.jpeg',
1428
+ imageVariations: {
1429
+ //@ts-expect-error - wrong type
1430
+ original: {
1431
+ width: 100,
1432
+ url: 'https://www.squiz.net',
1433
+ byteSize: 100,
1434
+ mimeType: 'image/jpeg',
1435
+ sha1Hash: '123',
1436
+ },
1437
+ },
1438
+ },
1439
+ //@ts-expect-error - wrong type
1440
+ link: {
1441
+ text: 'test-link',
1442
+ target: '_blank',
1443
+ },
1444
+ text: formattedText,
1445
+ };
1446
+
1447
+ expectToThrowErrorMatchingTypeAndMessage(
1448
+ () => jsonSchemaService.validateInput(input, schema),
1449
+ SchemaValidationError,
1450
+ 'failed validation: Value `{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}` in `#/image` does not match any given oneof schema,\nValue `{"text":"test-link","target":"_blank"}` in `#/link` does not match any given oneof schema,\nValue `[{"children":[{"type":"text","value":"hello"}]}]` in `#/text` does not match any given oneof schema',
1451
+ {
1452
+ '#/image': [
1453
+ {
1454
+ data: {
1455
+ errors: [
1456
+ {
1457
+ code: 'required-property-error',
1458
+ data: { key: 'height', pointer: '#/image/imageVariations/original' },
1459
+ message: 'The required property `height` is missing at `#/image/imageVariations/original`',
1460
+ name: 'RequiredPropertyError',
1461
+ type: 'error',
1462
+ },
1463
+ {
1464
+ code: 'required-property-error',
1465
+ data: { key: 'aspectRatio', pointer: '#/image/imageVariations/original' },
1466
+ message: 'The required property `aspectRatio` is missing at `#/image/imageVariations/original`',
1467
+ name: 'RequiredPropertyError',
1468
+ type: 'error',
1469
+ },
1470
+ ],
1471
+ oneOf: [{ $ref: 'SquizImage.json' }],
1472
+ pointer: '#/image',
1473
+ value:
1474
+ '{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}',
1475
+ },
1476
+ message:
1477
+ 'Value `{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}` in `#/image` does not match any given oneof schema',
1478
+ },
1479
+ ],
1480
+ '#/link': [
1481
+ {
1482
+ data: {
1483
+ errors: [
1484
+ {
1485
+ code: 'required-property-error',
1486
+ data: { key: 'url', pointer: '#/link' },
1487
+ message: 'The required property `url` is missing at `#/link`',
1488
+ name: 'RequiredPropertyError',
1489
+ type: 'error',
1490
+ },
1491
+ ],
1492
+ oneOf: [{ $ref: 'SquizLink.json' }],
1493
+ pointer: '#/link',
1494
+ value: '{"text":"test-link","target":"_blank"}',
1495
+ },
1496
+ message: 'Value `{"text":"test-link","target":"_blank"}` in `#/link` does not match any given oneof schema',
1497
+ },
1498
+ ],
1499
+ '#/text': [
1500
+ {
1501
+ data: {
1502
+ errors: [
1503
+ {
1504
+ code: 'any-of-error',
1505
+ data: {
1506
+ anyOf: [
1507
+ { $ref: '#/definitions/HigherOrderFormattedNodes' },
1508
+ { $ref: '#/definitions/BaseFormattedNodes' },
1509
+ ],
1510
+ pointer: '#/text/0',
1511
+ value: { children: [{ type: 'text', value: 'hello' }] },
1512
+ },
1513
+ message: 'Object at `#/text/0` does not match any schema',
1514
+ name: 'AnyOfError',
1515
+ type: 'error',
1516
+ },
1517
+ ],
1518
+ oneOf: [{ $ref: 'FormattedText.json' }],
1519
+ pointer: '#/text',
1520
+ value: '[{"children":[{"type":"text","value":"hello"}]}]',
1521
+ },
1522
+ message:
1523
+ 'Value `[{"children":[{"type":"text","value":"hello"}]}]` in `#/text` does not match any given oneof schema',
1524
+ },
1525
+ ],
1526
+ },
1527
+ );
1528
+ });
1529
+
1530
+ it('should catch validation errors when invalid matrix-asset-uri is provided with invalid other squiz primitive types ', () => {
1531
+ const schema = {
1532
+ type: 'object',
1533
+ properties: {
1534
+ image: { type: 'SquizImage' },
1535
+ link: { type: 'SquizLink' },
1536
+ text: { type: 'FormattedText' },
1537
+ asset: {
1538
+ type: 'string',
1539
+ format: 'matrix-asset-uri',
1540
+ },
1541
+ },
1542
+
1543
+ required: ['image', 'link', 'text', 'asset'],
1544
+ };
1545
+ const formattedText: FormattedText = [
1546
+ //@ts-expect-error - wrong type
1547
+ {
1548
+ children: [{ type: 'text', value: 'hello' }],
1549
+ },
1550
+ ];
1551
+ const input: {
1552
+ image: SquizImageType['__shape__'];
1553
+ link: SquizLinkType['__shape__'];
1554
+ text: FormattedText;
1555
+ asset: MatrixAssetUri;
1556
+ } = {
1557
+ image: {
1558
+ name: 'test-image.jpeg',
1559
+ imageVariations: {
1560
+ //@ts-expect-error - wrong type
1561
+ original: {
1562
+ width: 100,
1563
+ url: 'https://www.squiz.net',
1564
+ byteSize: 100,
1565
+ mimeType: 'image/jpeg',
1566
+ sha1Hash: '123',
1567
+ },
1568
+ },
1569
+ },
1570
+ //@ts-expect-error - wrong type
1571
+ link: {
1572
+ text: 'test-link',
1573
+ target: '_blank',
1574
+ },
1575
+ text: formattedText,
1576
+ // @ts-expect-error - wrong type
1577
+ asset: 'matrix://123',
1578
+ };
1579
+
1580
+ expectToThrowErrorMatchingTypeAndMessage(
1581
+ () => jsonSchemaService.validateInput(input, schema),
1582
+ SchemaValidationError,
1583
+ 'failed validation: Value `{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}` in `#/image` does not match any given oneof schema,\nValue `{"text":"test-link","target":"_blank"}` in `#/link` does not match any given oneof schema,\nValue `[{"children":[{"type":"text","value":"hello"}]}]` in `#/text` does not match any given oneof schema,\nValue matrix-asset-uri (matrix://123) in `#/asset` is not a valid matrix asset uri',
1584
+ {
1585
+ '#/asset': [
1586
+ {
1587
+ data: {
1588
+ errors: {
1589
+ assetId: {
1590
+ data: { expected: /^\d+(?::.+)?$/, received: '' },
1591
+ message: 'Matrix Asset Id has invalid format, must match /^d+(?::.+)?$/',
1592
+ },
1593
+ scheme: {
1594
+ data: { expected: 'matrix-asset', received: 'matrix' },
1595
+ message: 'Uri scheme is invalid, must match "matrix-asset"',
1596
+ },
1597
+ },
1598
+ pointer: '#/asset',
1599
+ value: 'matrix://123',
1600
+ },
1601
+ message: 'Value matrix-asset-uri (matrix://123) in `#/asset` is not a valid matrix asset uri',
1602
+ },
1603
+ ],
1604
+ '#/image': [
1605
+ {
1606
+ data: {
1607
+ errors: [
1608
+ {
1609
+ code: 'required-property-error',
1610
+ data: { key: 'height', pointer: '#/image/imageVariations/original' },
1611
+ message: 'The required property `height` is missing at `#/image/imageVariations/original`',
1612
+ name: 'RequiredPropertyError',
1613
+ type: 'error',
1614
+ },
1615
+ {
1616
+ code: 'required-property-error',
1617
+ data: { key: 'aspectRatio', pointer: '#/image/imageVariations/original' },
1618
+ message: 'The required property `aspectRatio` is missing at `#/image/imageVariations/original`',
1619
+ name: 'RequiredPropertyError',
1620
+ type: 'error',
1621
+ },
1622
+ ],
1623
+ oneOf: [{ $ref: 'SquizImage.json' }],
1624
+ pointer: '#/image',
1625
+ value:
1626
+ '{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}',
1627
+ },
1628
+ message:
1629
+ 'Value `{"name":"test-image.jpeg","imageVariations":{"original":{"width":100,"url":"https://www.squiz.net","byteSize":100,"mimeType":"image/jpeg","sha1Hash":"123"}}}` in `#/image` does not match any given oneof schema',
1630
+ },
1631
+ ],
1632
+ '#/link': [
1633
+ {
1634
+ data: {
1635
+ errors: [
1636
+ {
1637
+ code: 'required-property-error',
1638
+ data: { key: 'url', pointer: '#/link' },
1639
+ message: 'The required property `url` is missing at `#/link`',
1640
+ name: 'RequiredPropertyError',
1641
+ type: 'error',
1642
+ },
1643
+ ],
1644
+ oneOf: [{ $ref: 'SquizLink.json' }],
1645
+ pointer: '#/link',
1646
+ value: '{"text":"test-link","target":"_blank"}',
1647
+ },
1648
+ message: 'Value `{"text":"test-link","target":"_blank"}` in `#/link` does not match any given oneof schema',
1649
+ },
1650
+ ],
1651
+ '#/text': [
1652
+ {
1653
+ data: {
1654
+ errors: [
1655
+ {
1656
+ code: 'any-of-error',
1657
+ data: {
1658
+ anyOf: [
1659
+ { $ref: '#/definitions/HigherOrderFormattedNodes' },
1660
+ { $ref: '#/definitions/BaseFormattedNodes' },
1661
+ ],
1662
+ pointer: '#/text/0',
1663
+ value: { children: [{ type: 'text', value: 'hello' }] },
1664
+ },
1665
+ message: 'Object at `#/text/0` does not match any schema',
1666
+ name: 'AnyOfError',
1667
+ type: 'error',
1668
+ },
1669
+ ],
1670
+ oneOf: [{ $ref: 'FormattedText.json' }],
1671
+ pointer: '#/text',
1672
+ value: '[{"children":[{"type":"text","value":"hello"}]}]',
1673
+ },
1674
+ message:
1675
+ 'Value `[{"children":[{"type":"text","value":"hello"}]}]` in `#/text` does not match any given oneof schema',
1676
+ },
1677
+ ],
1678
+ },
1679
+ );
1680
+ });
1681
+
1682
+ it('should validate when MatrixAssetType is being used for a squiz primitive type with resolver', () => {
1683
+ const typeResolver = TypeResolverBuilder.new()
1684
+ .addPrimitive(SquizImageType)
1685
+ .addPrimitive(SquizLinkType)
1686
+ .addPrimitive(FormattedTextType)
1687
+ .addResolver(SquizImageType, MatrixAssetType, (): SquizImageType['__shape__'] => {
1688
+ return {
1689
+ name: '',
1690
+ imageVariations: {
1691
+ original: {
1692
+ width: 0,
1693
+ height: 0,
1694
+ url: '',
1695
+ mimeType: '',
1696
+ byteSize: 0,
1697
+ sha1Hash: '',
1698
+ aspectRatio: '',
1699
+ },
1700
+ },
1701
+ };
1702
+ })
1703
+ .build();
1704
+
1705
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
1706
+ const schema = {
1707
+ type: 'object',
1708
+ properties: {
1709
+ image: { type: 'SquizImage' },
1710
+ link: { type: 'SquizLink' },
1711
+ text: { type: 'FormattedText' },
1712
+ asset: {
1713
+ type: 'string',
1714
+ format: 'matrix-asset-uri',
1715
+ },
1716
+ },
1717
+ required: ['image', 'link', 'text', 'asset'],
1718
+ };
1719
+ const formattedText: FormattedText = [
1720
+ {
1721
+ tag: 'p',
1722
+ type: 'tag',
1723
+ children: [{ type: 'text', value: 'hello' }],
1724
+ },
1725
+ ];
1726
+ const input: {
1727
+ image: MatrixAssetType['__shape__'];
1728
+ link: SquizLinkType['__shape__'];
1729
+ text: FormattedText;
1730
+ asset: MatrixAssetUri;
1731
+ } = {
1732
+ image: {
1733
+ matrixAssetId: '123',
1734
+ matrixIdentifier: 'identifier',
1735
+ matrixDomain: 'domain',
1736
+ },
1737
+ link: {
1738
+ text: 'test-link',
1739
+ url: 'https://www.squiz.net',
1740
+ target: '_blank',
1741
+ },
1742
+ text: formattedText,
1743
+ asset: 'matrix-asset://identifier/123',
1744
+ };
1745
+
1746
+ expect(jsonSchemaService.validateInput(input, schema)).toEqual(true);
1747
+ });
1748
+
1749
+ it('it should catch MatrixAssetType validation errors when being use for squiz primitive type with resolver', () => {
1750
+ const typeResolver = TypeResolverBuilder.new()
1751
+ .addPrimitive(SquizImageType)
1752
+ .addPrimitive(SquizLinkType)
1753
+ .addPrimitive(FormattedTextType)
1754
+ .addResolver(SquizImageType, MatrixAssetType, (): SquizImageType['__shape__'] => {
1755
+ return {
1756
+ name: '',
1757
+ imageVariations: {
1758
+ original: {
1759
+ width: 0,
1760
+ height: 0,
1761
+ url: '',
1762
+ mimeType: '',
1763
+ byteSize: 0,
1764
+ sha1Hash: '',
1765
+ aspectRatio: '',
1766
+ },
1767
+ },
1768
+ };
1769
+ })
1770
+ .build();
1771
+
1772
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
1773
+ const schema = {
1774
+ type: 'object',
1775
+ properties: {
1776
+ image: { type: 'SquizImage' },
1777
+ },
1778
+ required: ['image'],
1779
+ };
1780
+ const input: {
1781
+ image: MatrixAssetType['__shape__'];
1782
+ } = {
1783
+ //@ts-expect-error - intentionally invalid input
1784
+ image: {
1785
+ matrixIdentifier: 'identifier',
1786
+ },
1787
+ };
1788
+ expectToThrowErrorMatchingTypeAndMessage(
1789
+ () => jsonSchemaService.validateInput(input, schema),
1790
+ SchemaValidationError,
1791
+ 'failed validation: Value `{"matrixIdentifier":"identifier"}` in `#/image` does not match any given oneof schema',
1792
+ {
1793
+ '#/image': [
1794
+ {
1795
+ data: {
1796
+ errors: [
1797
+ {
1798
+ code: 'required-property-error',
1799
+ data: { key: 'name', pointer: '#/image' },
1800
+ message: 'The required property `name` is missing at `#/image`',
1801
+ name: 'RequiredPropertyError',
1802
+ type: 'error',
1803
+ },
1804
+ {
1805
+ code: 'required-property-error',
1806
+ data: { key: 'imageVariations', pointer: '#/image' },
1807
+ message: 'The required property `imageVariations` is missing at `#/image`',
1808
+ name: 'RequiredPropertyError',
1809
+ type: 'error',
1810
+ },
1811
+ {
1812
+ code: 'required-property-error',
1813
+ data: { key: 'matrixAssetId', pointer: '#/image' },
1814
+ message: 'The required property `matrixAssetId` is missing at `#/image`',
1815
+ name: 'RequiredPropertyError',
1816
+ type: 'error',
1817
+ },
1818
+ ],
1819
+ oneOf: [{ $ref: 'SquizImage.json' }, { $ref: 'MatrixAsset.json' }],
1820
+ pointer: '#/image',
1821
+ value: '{"matrixIdentifier":"identifier"}',
1822
+ },
1823
+ message: 'Value `{"matrixIdentifier":"identifier"}` in `#/image` does not match any given oneof schema',
1824
+ },
1825
+ ],
1826
+ },
1827
+ );
1828
+ });
1829
+
1830
+ it('should only resolve an array of items containing resolvable types, other arrays are unaffected', async () => {
1831
+ const typeResolver = TypeResolverBuilder.new()
1832
+ .addPrimitive(SquizImageType)
1833
+ .addPrimitive(SquizLinkType)
1834
+ .addPrimitive(FormattedTextType)
1835
+ .addResolver(SquizImageType, MatrixAssetType, (): SquizImageType['__shape__'] => {
1836
+ return {
1837
+ name: '',
1838
+ imageVariations: {
1839
+ original: {
1840
+ width: 0,
1841
+ height: 0,
1842
+ url: '',
1843
+ mimeType: '',
1844
+ byteSize: 0,
1845
+ sha1Hash: '',
1846
+ aspectRatio: '',
1847
+ },
1848
+ },
1849
+ };
1850
+ })
1851
+ .addResolver(SquizLinkType, MatrixAssetLinkType, (): SquizLinkType['__shape__'] => {
1852
+ return {
1853
+ text: 'link text',
1854
+ url: 'www.test.com',
1855
+ target: '_self',
1856
+ };
1857
+ })
1858
+ .build();
1859
+
1860
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
1861
+
1862
+ const schema = {
1863
+ type: 'object',
1864
+ properties: {
1865
+ images: {
1866
+ description: 'Gallery images',
1867
+ type: 'array',
1868
+ items: {
1869
+ type: 'SquizImage',
1870
+ },
1871
+ },
1872
+ squizLink: {
1873
+ title: 'Squiz link',
1874
+ type: 'array',
1875
+ items: {
1876
+ type: 'SquizLink',
1877
+ },
1878
+ },
1879
+ //custom format
1880
+ multiLine: {
1881
+ title: 'Multi-line (textarea)',
1882
+ type: 'array',
1883
+ items: {
1884
+ type: 'string',
1885
+ format: 'multi-line',
1886
+ },
1887
+ },
1888
+ //string array
1889
+ listOfStrings: {
1890
+ type: 'array',
1891
+ title: 'A list of strings',
1892
+ items: {
1893
+ type: 'string',
1894
+ default: 'Holy smokes',
1895
+ },
1896
+ },
1897
+ },
1898
+ required: ['images', 'links'],
1899
+ };
1900
+
1901
+ const input: {
1902
+ images: Array<MatrixAssetType['__shape__']>;
1903
+ squizLink: Array<MatrixAssetLinkType['__shape__']>;
1904
+ multiline: Array<string>;
1905
+ listOfStrings: Array<string>;
1906
+ } = {
1907
+ images: [
1908
+ {
1909
+ matrixDomain: 'https://feaas-page-render-us.dev.matrix.squiz.cloud/59',
1910
+ matrixAssetId: '160',
1911
+ matrixIdentifier: 'feaas-matrix-us',
1912
+ },
1913
+ ],
1914
+ squizLink: [
1915
+ {
1916
+ matrixDomain: 'https://feaas-page-render-us.dev.matrix.squiz.cloud/59',
1917
+ matrixAssetId: '160',
1918
+ matrixIdentifier: 'feaas-matrix-us',
1919
+ target: '_self',
1920
+ },
1921
+ ],
1922
+ multiline: ['wow', 'much', 'multiline'],
1923
+ listOfStrings: ['very', 'string'],
1924
+ };
1925
+ const result = await jsonSchemaService.resolveInput(input, schema);
1926
+ expect(result).toEqual({
1927
+ images: [
1928
+ {
1929
+ imageVariations: {
1930
+ original: { aspectRatio: '', byteSize: 0, height: 0, mimeType: '', sha1Hash: '', url: '', width: 0 },
1931
+ },
1932
+ name: '',
1933
+ },
1934
+ ],
1935
+ squizLink: [
1936
+ {
1937
+ text: 'link text',
1938
+ url: 'www.test.com',
1939
+ target: '_self',
1940
+ },
1941
+ ],
1942
+ multiline: ['wow', 'much', 'multiline'],
1943
+ listOfStrings: ['very', 'string'],
1944
+ });
1945
+ });
1946
+
1947
+ it('should resolve multiple primitive type array items in multi-level nested array structures', async () => {
1948
+ const typeResolver = TypeResolverBuilder.new()
1949
+ .addPrimitive(SquizLinkType)
1950
+ .addResolver(
1951
+ SquizLinkType,
1952
+ MatrixAssetLinkType,
1953
+ (input: MatrixAssetLinkType['__shape__']): SquizLinkType['__shape__'] => {
1954
+ if ('matrixIdentifier' in input) {
1955
+ return {
1956
+ text: 'link text',
1957
+ url: `www.test.com/asset/${input.matrixAssetId}`,
1958
+ target: input.target,
1959
+ };
1960
+ }
1961
+
1962
+ return input;
1963
+ },
1964
+ )
1965
+ .build();
1966
+
1967
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
1968
+
1969
+ const schema = {
1970
+ type: 'object',
1971
+ properties: {
1972
+ object1: {
1973
+ type: 'object',
1974
+ required: [],
1975
+ properties: {
1976
+ array1: {
1977
+ type: 'array',
1978
+ items: {
1979
+ type: 'object',
1980
+ properties: {
1981
+ linksArray: {
1982
+ type: 'array',
1983
+ items: {
1984
+ type: 'SquizLink',
1985
+ },
1986
+ },
1987
+ },
1988
+ },
1989
+ },
1990
+ },
1991
+ },
1992
+ },
1993
+ };
1994
+
1995
+ const input: {
1996
+ object1: {
1997
+ array1: Array<{
1998
+ linksArray: Array<SquizLinkType['__shape__'] | MatrixAssetLinkType['__shape__']>;
1999
+ }>;
2000
+ };
2001
+ } = {
2002
+ object1: {
2003
+ array1: [
2004
+ {
2005
+ linksArray: [
2006
+ {
2007
+ url: '#LineOne',
2008
+ text: 'Link One',
2009
+ target: '_blank',
2010
+ },
2011
+ {
2012
+ url: '#LineTwo',
2013
+ text: 'Link Two',
2014
+ target: '_blank',
2015
+ },
2016
+ {
2017
+ matrixAssetId: '100',
2018
+ matrixDomain: 'my-matrix.squiz.net',
2019
+ matrixIdentifier: 'my-matrix-identifier',
2020
+ target: '_blank',
2021
+ },
2022
+ ],
2023
+ },
2024
+ ],
2025
+ },
2026
+ };
2027
+ const result = await jsonSchemaService.resolveInput(input, schema);
2028
+ expect(result).toEqual({
2029
+ object1: {
2030
+ array1: [
2031
+ {
2032
+ linksArray: [
2033
+ {
2034
+ url: '#LineOne',
2035
+ text: 'Link One',
2036
+ target: '_blank',
2037
+ },
2038
+ {
2039
+ url: '#LineTwo',
2040
+ text: 'Link Two',
2041
+ target: '_blank',
2042
+ },
2043
+ {
2044
+ url: 'www.test.com/asset/100',
2045
+ text: 'link text',
2046
+ target: '_blank',
2047
+ },
2048
+ ],
2049
+ },
2050
+ ],
2051
+ },
2052
+ });
2053
+ });
2054
+
2055
+ it('should not use the resolver for primitive items in a resolvable array', async () => {
2056
+ const mockMatrixAssetResolver = jest.fn((input: MatrixAssetType['__shape__']) => {
2057
+ return {
2058
+ name: input.matrixAssetId,
2059
+ imageVariations: {
2060
+ original: {
2061
+ width: 0,
2062
+ height: 0,
2063
+ url: '',
2064
+ mimeType: '',
2065
+ byteSize: 0,
2066
+ sha1Hash: '',
2067
+ aspectRatio: '',
2068
+ },
2069
+ },
2070
+ };
2071
+ });
2072
+ const mockMatrixLinkResolver = jest.fn((input: MatrixAssetLinkType['__shape__']): SquizLinkType['__shape__'] => {
2073
+ return {
2074
+ text: input.matrixAssetId,
2075
+ url: 'www.test.com',
2076
+ target: '_self',
2077
+ };
2078
+ });
2079
+ const typeResolver = TypeResolverBuilder.new()
2080
+ .addPrimitive(SquizImageType)
2081
+ .addPrimitive(SquizLinkType)
2082
+ .addPrimitive(FormattedTextType)
2083
+ .addResolver(SquizImageType, MatrixAssetType, mockMatrixAssetResolver)
2084
+ .addResolver(SquizLinkType, MatrixAssetLinkType, mockMatrixLinkResolver)
2085
+ .build();
2086
+
2087
+ const jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema);
2088
+
2089
+ const schema = {
2090
+ type: 'object',
2091
+ properties: {
2092
+ images: {
2093
+ description: 'Gallery images',
2094
+ type: 'array',
2095
+ items: {
2096
+ type: 'SquizImage',
2097
+ },
2098
+ },
2099
+ squizLink: {
2100
+ title: 'Squiz link',
2101
+ type: 'array',
2102
+ items: {
2103
+ type: 'SquizLink',
2104
+ },
2105
+ },
2106
+ //custom format
2107
+ multiLine: {
2108
+ title: 'Multi-line (textarea)',
2109
+ type: 'array',
2110
+ items: {
2111
+ type: 'string',
2112
+ format: 'multi-line',
2113
+ },
2114
+ },
2115
+ //string array
2116
+ listOfStrings: {
2117
+ type: 'array',
2118
+ title: 'A list of strings',
2119
+ items: {
2120
+ type: 'string',
2121
+ default: 'Holy smokes',
2122
+ },
2123
+ },
2124
+ },
2125
+ required: ['images', 'links'],
2126
+ };
2127
+
2128
+ const input: {
2129
+ images: Array<MatrixAssetType['__shape__'] | SquizImageType['__shape__']>;
2130
+ squizLink: Array<MatrixAssetLinkType['__shape__'] | SquizLinkType['__shape__']>;
2131
+ multiline: Array<string>;
2132
+ listOfStrings: Array<string>;
2133
+ } = {
2134
+ images: [
2135
+ {
2136
+ matrixDomain: 'https://feaas-page-render-us.dev.matrix.squiz.cloud/59',
2137
+ matrixAssetId: '160',
2138
+ matrixIdentifier: 'feaas-matrix-us',
2139
+ },
2140
+ {
2141
+ imageVariations: {
2142
+ original: { aspectRatio: '', byteSize: 0, height: 0, mimeType: '', sha1Hash: '', url: '', width: 0 },
2143
+ },
2144
+ name: '',
2145
+ },
2146
+ ],
2147
+ squizLink: [
2148
+ {
2149
+ matrixDomain: 'https://feaas-page-render-us.dev.matrix.squiz.cloud/59',
2150
+ matrixAssetId: '160',
2151
+ matrixIdentifier: 'feaas-matrix-us',
2152
+ target: '_self',
2153
+ },
2154
+ {
2155
+ text: 'link text',
2156
+ url: 'www.test.com',
2157
+ target: '_self',
2158
+ },
2159
+ ],
2160
+ multiline: ['wow', 'much', 'multiline'],
2161
+ listOfStrings: ['very', 'string'],
2162
+ };
2163
+ const result = await jsonSchemaService.resolveInput(input, schema);
2164
+ expect(result).toEqual({
2165
+ images: [
2166
+ {
2167
+ imageVariations: {
2168
+ original: { aspectRatio: '', byteSize: 0, height: 0, mimeType: '', sha1Hash: '', url: '', width: 0 },
2169
+ },
2170
+ name: (input.images[0] as MatrixAssetType['__shape__']).matrixAssetId,
2171
+ },
2172
+ {
2173
+ imageVariations: {
2174
+ original: { aspectRatio: '', byteSize: 0, height: 0, mimeType: '', sha1Hash: '', url: '', width: 0 },
2175
+ },
2176
+ name: '',
2177
+ },
2178
+ ],
2179
+ squizLink: [
2180
+ {
2181
+ text: (input.squizLink[0] as MatrixAssetLinkType['__shape__']).matrixAssetId,
2182
+ url: 'www.test.com',
2183
+ target: '_self',
2184
+ },
2185
+ {
2186
+ text: 'link text',
2187
+ url: 'www.test.com',
2188
+ target: '_self',
2189
+ },
2190
+ ],
2191
+ multiline: ['wow', 'much', 'multiline'],
2192
+ listOfStrings: ['very', 'string'],
2193
+ });
2194
+
2195
+ expect(mockMatrixAssetResolver).toHaveBeenCalledTimes(1);
2196
+ expect(mockMatrixLinkResolver).toHaveBeenCalledTimes(1);
2197
+ });
2198
+ });