@remoteoss/json-schema-form 0.11.11-dev.20250220174843 → 1.0.0-beta.0

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.
@@ -1,803 +0,0 @@
1
- import merge from 'lodash/fp/merge';
2
-
3
- import { JSONSchemaBuilder, mockFieldset, mockRadioInputString } from './helpers';
4
- import { mockMoneyInput } from './helpers.custom';
5
- import { createHeadlessForm } from '@/createHeadlessForm';
6
-
7
- function friendlyError({ formErrors }) {
8
- // destruct the formErrors directly
9
- return formErrors;
10
- }
11
-
12
- export const mockNumberInput = {
13
- title: 'Tabs',
14
- description: 'How many open tabs do you have?',
15
- 'x-jsf-presentation': {
16
- inputType: 'number',
17
- },
18
- minimum: 5,
19
- maximum: 30,
20
- type: 'number',
21
- };
22
-
23
- export const mockNumberInputDeprecatedPresentation = {
24
- title: 'Tabs',
25
- description: 'How many open tabs do you have?',
26
- presentation: {
27
- inputType: 'number',
28
- },
29
- minimum: 5,
30
- maximum: 30,
31
- type: 'number',
32
- };
33
-
34
- const schemaBasic = ({ newProperties, allOf } = {}) =>
35
- JSONSchemaBuilder()
36
- .addInput(
37
- merge(
38
- {
39
- parent_age: { ...mockNumberInput, maximum: 100 },
40
- child_age: mockNumberInput,
41
- },
42
- newProperties
43
- )
44
- )
45
- .setRequiredFields(['parent_age'])
46
- .addAllOf(allOf || [])
47
- .build();
48
-
49
- const schemaWithConditional = ({ newProperties } = {}) =>
50
- JSONSchemaBuilder()
51
- .addInput(
52
- merge(
53
- {
54
- is_employee: mockRadioInputString,
55
- salary: { ...mockMoneyInput, minimum: 0 },
56
- bonus: { ...mockMoneyInput, minimum: 0 },
57
- },
58
- newProperties
59
- )
60
- )
61
- .setRequiredFields(['is_employee', 'salary'])
62
- .addAllOf([
63
- {
64
- if: {
65
- properties: {
66
- is_employee: {
67
- const: 'yes',
68
- },
69
- },
70
- required: ['is_employee'],
71
- },
72
- then: {
73
- properties: {
74
- salary: {
75
- minimum: 100000, // 1000.00€
76
- },
77
- },
78
- required: ['bonus'],
79
- },
80
- else: {
81
- properties: {
82
- salary: {
83
- minimum: 0, // 0.00€
84
- },
85
- bonus: false,
86
- },
87
- },
88
- },
89
- ])
90
- .build();
91
-
92
- function validateFieldParams(fieldParams, newFieldParams) {
93
- expect(newFieldParams).toHaveProperty('name', fieldParams.name);
94
- expect(newFieldParams).toHaveProperty('label', fieldParams.title);
95
- expect(newFieldParams).toHaveProperty('description', fieldParams.description);
96
-
97
- if (fieldParams.minimum) {
98
- expect(newFieldParams).toHaveProperty('minimum', fieldParams.minimum);
99
- }
100
- if (fieldParams.maximum) {
101
- expect(newFieldParams).toHaveProperty('maximum', fieldParams.maximum);
102
- }
103
- }
104
-
105
- function validateNumberParams(fieldParams, newFieldParams) {
106
- validateFieldParams(fieldParams, newFieldParams);
107
- expect(newFieldParams).toHaveProperty('inputType', 'number');
108
- expect(newFieldParams).toHaveProperty('jsonType', 'number');
109
- }
110
-
111
- function validateMoneyParams(fieldParams, newFieldParams) {
112
- validateFieldParams(fieldParams, newFieldParams);
113
- expect(newFieldParams).toHaveProperty('inputType', 'money');
114
- expect(newFieldParams).toHaveProperty('jsonType', 'integer');
115
- }
116
-
117
- function createScenario({ schema, config }) {
118
- const form = createHeadlessForm(schema, config);
119
- const validateForm = (vals) => friendlyError(form.handleValidation(vals));
120
-
121
- return {
122
- ...form,
123
- validateForm,
124
- };
125
- }
126
-
127
- beforeAll(() => {
128
- jest.spyOn(console, 'warn').mockImplementation(() => {});
129
- });
130
-
131
- afterEach(() => {
132
- // safety-check that every mocked validation is within the range
133
- // eslint-disable-next-line no-console
134
- expect(console.warn).not.toHaveBeenCalled();
135
- });
136
-
137
- afterAll(() => {
138
- // eslint-disable-next-line no-console
139
- console.warn.mockRestore();
140
- });
141
-
142
- // @deprecated - customProperties won't be supported in v2.
143
- describe('createHeadlessForm() - custom validations (deprecated)', () => {
144
- describe('simple validation (eg maximum)', () => {
145
- it('works as a number', () => {
146
- const { fields, validateForm } = createScenario({
147
- schema: schemaBasic(),
148
- config: {
149
- customProperties: {
150
- child_age: {
151
- maximum: 14,
152
- },
153
- },
154
- },
155
- });
156
-
157
- validateNumberParams({ ...mockNumberInput, name: 'child_age', maximum: 14 }, fields[1]);
158
-
159
- expect(validateForm({})).toEqual({
160
- parent_age: 'Required field',
161
- });
162
-
163
- expect(validateForm({ parent_age: 30, child_age: 15 })).toEqual({
164
- child_age: 'Must be smaller or equal to 14',
165
- });
166
-
167
- expect(validateForm({ parent_age: 30, child_age: 10 })).toBeUndefined();
168
- });
169
-
170
- it('works as a function', () => {
171
- // Friendly Scenario: child_age must be smaller than parent_age.
172
- const { fields, validateForm } = createScenario({
173
- schema: schemaBasic(),
174
- config: {
175
- customProperties: {
176
- child_age: {
177
- maximum: (values, { maximum }) => values.parent_age || maximum,
178
- },
179
- },
180
- },
181
- });
182
-
183
- validateNumberParams(
184
- { ...mockNumberInput, name: 'child_age', maximum: undefined },
185
- fields[1]
186
- );
187
-
188
- expect(validateForm({})).toEqual({
189
- parent_age: 'Required field',
190
- });
191
-
192
- expect(validateForm({ parent_age: 25, child_age: 26 })).toEqual({
193
- child_age: 'Must be smaller or equal to 25',
194
- });
195
- expect(validateForm({ parent_age: 25, child_age: 20 })).toBeUndefined();
196
- });
197
-
198
- it('works with minimum and maximum together', () => {
199
- const { fields, validateForm } = createScenario({
200
- schema: schemaBasic(),
201
- config: {
202
- customProperties: {
203
- child_age: {
204
- // dumb example: parents that are less than double the child age,
205
- // the child must be between 20 and 29yo.
206
- minimum: (values, { minimum }) =>
207
- values.parent_age < values.child_age * 3 ? 20 : minimum,
208
- maximum: (values, { maximum }) =>
209
- values.parent_age < values.child_age * 3 ? 29 : maximum,
210
- },
211
- },
212
- },
213
- });
214
-
215
- validateNumberParams(
216
- { ...mockNumberInput, name: 'child_age', minimum: 5, maximum: 30 },
217
- fields[1]
218
- );
219
-
220
- // Test the default validations
221
- expect(validateForm({ parent_age: 50, child_age: 1 })).toEqual({
222
- child_age: 'Must be greater or equal to 5',
223
- });
224
- expect(validateForm({ parent_age: 100, child_age: 31 })).toEqual({
225
- child_age: 'Must be smaller or equal to 30',
226
- });
227
-
228
- // Test the custom validations
229
- expect(validateForm({ parent_age: 35, child_age: 19 })).toEqual({
230
- child_age: 'Must be greater or equal to 20',
231
- });
232
- expect(validateForm({ parent_age: 40, child_age: 31 })).toEqual({
233
- child_age: 'Must be smaller or equal to 29',
234
- });
235
- });
236
-
237
- it('works with negative values', () => {
238
- const { fields, validateForm } = createScenario({
239
- schema: schemaBasic({
240
- newProperties: {
241
- parent_age: {
242
- minimum: -20,
243
- maximum: -1,
244
- },
245
- },
246
- }),
247
- config: {
248
- customProperties: {
249
- parent_age: {
250
- minimum: -15,
251
- maximum: -5,
252
- },
253
- },
254
- },
255
- });
256
-
257
- validateNumberParams(
258
- { ...mockNumberInput, name: 'parent_age', minimum: -15, maximum: -5 },
259
- fields[0]
260
- );
261
-
262
- expect(validateForm({})).toEqual({
263
- parent_age: 'Required field',
264
- });
265
-
266
- expect(validateForm({ parent_age: -20 })).toEqual({
267
- parent_age: 'Must be greater or equal to -15',
268
- });
269
-
270
- expect(validateForm({ parent_age: -4 })).toEqual({
271
- parent_age: 'Must be smaller or equal to -5',
272
- });
273
-
274
- expect(validateForm({ parent_age: -10 })).toBeUndefined();
275
- });
276
-
277
- it('keeps original validation, given an empty validation', () => {
278
- const { fields, validateForm } = createScenario({
279
- schema: schemaBasic(),
280
- config: {
281
- customProperties: {
282
- parent_age: {},
283
- },
284
- },
285
- });
286
-
287
- validateNumberParams({ ...mockNumberInput, name: 'parent_age', maximum: 100 }, fields[0]);
288
-
289
- expect(validateForm({})).toEqual({
290
- parent_age: 'Required field',
291
- });
292
-
293
- expect(validateForm({ parent_age: 0 })).toEqual({
294
- parent_age: 'Must be greater or equal to 5',
295
- });
296
- });
297
-
298
- it('applies validation, when original does not exist', () => {
299
- const { fields, validateForm } = createScenario({
300
- schema: schemaBasic({
301
- newProperties: {
302
- parent_age: { minimum: null, maximum: null },
303
- },
304
- }),
305
- config: {
306
- customProperties: {
307
- parent_age: {
308
- minimum: 1,
309
- maximum: 20,
310
- },
311
- },
312
- },
313
- });
314
-
315
- validateNumberParams(
316
- { ...mockNumberInput, minimum: 1, maximum: 20, name: 'parent_age' },
317
- fields[0]
318
- );
319
-
320
- expect(validateForm({})).toEqual({
321
- parent_age: 'Required field',
322
- });
323
-
324
- expect(validateForm({ parent_age: 0 })).toEqual({
325
- parent_age: 'Must be greater or equal to 1',
326
- });
327
-
328
- expect(validateForm({ parent_age: 21 })).toEqual({
329
- parent_age: 'Must be smaller or equal to 20',
330
- });
331
- });
332
- });
333
-
334
- describe('in fieldsets', () => {
335
- it('applies custom validation in nested fields', () => {
336
- const { fields, validateForm } = createScenario({
337
- schema: JSONSchemaBuilder()
338
- .addInput({
339
- animal_age: mockNumberInput,
340
- second_gen: {
341
- ...mockFieldset,
342
- properties: {
343
- cub_age: mockNumberInput,
344
- third_gen: {
345
- ...mockFieldset,
346
- properties: {
347
- grandcub_age: mockNumberInput,
348
- },
349
- },
350
- },
351
- },
352
- })
353
- .build(),
354
- config: {
355
- customProperties: {
356
- animal_age: {
357
- minimum: 24,
358
- maximum: 28,
359
- },
360
- second_gen: {
361
- customProperties: {
362
- cub_age: {
363
- minimum: 18,
364
- maximum: 21,
365
- },
366
- third_gen: {
367
- customProperties: {
368
- grandcub_age: {
369
- minimum: 10,
370
- maximum: 15,
371
- },
372
- },
373
- },
374
- },
375
- },
376
- },
377
- },
378
- });
379
-
380
- const [animalField, secondGenField] = fields;
381
-
382
- // Assert custom validations
383
- validateNumberParams(
384
- {
385
- ...mockNumberInput,
386
- name: 'animal_age',
387
- minimum: 24,
388
- maximum: 28,
389
- required: false,
390
- },
391
- animalField
392
- );
393
- validateNumberParams(
394
- {
395
- ...mockNumberInput,
396
- name: 'cub_age',
397
- minimum: 18,
398
- maximum: 21,
399
- required: false,
400
- },
401
- secondGenField.fields[0]
402
- );
403
- validateNumberParams(
404
- {
405
- ...mockNumberInput,
406
- name: 'grandcub_age',
407
- minimum: 10,
408
- maximum: 15,
409
- required: false,
410
- },
411
- secondGenField.fields[1].fields[0]
412
- );
413
-
414
- // Assert minimum values
415
- expect(
416
- validateForm({
417
- animal_age: 1,
418
- second_gen: {
419
- cub_age: 1,
420
- third_gen: {
421
- grandcub_age: 1,
422
- },
423
- },
424
- })
425
- ).toEqual({
426
- animal_age: 'Must be greater or equal to 24',
427
- second_gen: {
428
- cub_age: 'Must be greater or equal to 18',
429
- third_gen: {
430
- grandcub_age: 'Must be greater or equal to 10',
431
- },
432
- },
433
- });
434
-
435
- // Assert maximum values
436
- expect(
437
- validateForm({
438
- animal_age: 100,
439
- second_gen: {
440
- cub_age: 100,
441
- third_gen: {
442
- grandcub_age: 100,
443
- },
444
- },
445
- })
446
- ).toEqual({
447
- animal_age: 'Must be smaller or equal to 28',
448
- second_gen: {
449
- cub_age: 'Must be smaller or equal to 21',
450
- third_gen: {
451
- grandcub_age: 'Must be smaller or equal to 15',
452
- },
453
- },
454
- });
455
- });
456
- });
457
-
458
- describe('in conditional fields', () => {
459
- const { fields, validateForm } = createScenario({
460
- schema: schemaWithConditional(),
461
- config: {
462
- customProperties: {
463
- bonus: {
464
- maximum: (values, { maximum }) => ({
465
- maximum: values.salary ? values.salary * 2 : maximum,
466
- 'x-jsf-errorMessage': {
467
- maximum: `The bonus cannot be twice of the salary ${values.salary}.`,
468
- },
469
- }),
470
- },
471
- },
472
- },
473
- });
474
-
475
- it('validates conditional visible field', () => {
476
- // bonus fieldResult
477
- validateMoneyParams(
478
- {
479
- ...mockMoneyInput,
480
- name: 'bonus',
481
- minimum: 0,
482
- maximum: 500000,
483
- required: false,
484
- },
485
- fields[2]
486
- );
487
-
488
- // Basic path — the custom validation is triggered
489
- expect(
490
- validateForm({
491
- is_employee: 'yes',
492
- salary: 150000,
493
- bonus: 310000,
494
- })
495
- ).toEqual({ bonus: 'The bonus cannot be twice of the salary 150000.' });
496
-
497
- // The values are valid:
498
- expect(
499
- validateForm({
500
- is_employee: 'yes',
501
- salary: 150000,
502
- bonus: 20000,
503
- })
504
- ).toBeUndefined();
505
-
506
- expect(validateForm({ is_employee: 'yes', salary: 150000 })).toEqual({
507
- bonus: 'Required field',
508
- });
509
- });
510
-
511
- it('ignores validation to conditional hidden field', () => {
512
- expect(
513
- validateForm({
514
- is_employee: 'no',
515
- salary: 150000,
516
- bonus: 310000,
517
- // NOTE/Unrelated-bug: Should it throw an error saying this
518
- // "bonus" value is not expected? the native json schema spec throw an error...
519
- })
520
- ).toBeUndefined();
521
- });
522
-
523
- it('given an out-of-range validation, logs warning', () => {
524
- expect(
525
- validateForm({
526
- is_employee: 'yes',
527
- salary: 300000,
528
- bonus: 500100,
529
- })
530
- ).toEqual({
531
- bonus: 'No more than €5000.00',
532
- });
533
-
534
- // eslint-disable-next-line no-console
535
- expect(console.warn).toHaveBeenNthCalledWith(
536
- 1,
537
- 'Custom validation for bonus is not allowed because maximum:600000 is less strict than the original range: 0 to 500000'
538
- );
539
- // eslint-disable-next-line no-console
540
- console.warn.mockClear();
541
- });
542
- });
543
-
544
- // TODO: delete after migration to x-jsf-errorMessage is completed
545
- describe('with errorMessage (deprecated)', () => {
546
- /* NOTE: We have 3 type of errors:
547
- - original error: (created by json-schema-form)
548
- - errorMessage: (declared on JSON Schema)
549
- - customValidation.errorMessage: (declared on config)
550
- */
551
- it('overrides original error conditionally', () => {
552
- const { fields, validateForm } = createScenario({
553
- schema: schemaBasic(),
554
- config: {
555
- customProperties: {
556
- child_age: {
557
- maximum: (values, { maximum }) => ({
558
- maximum: values.parent_age || maximum,
559
- errorMessage: {
560
- maximum: `The child cannot be older than the parent of ${values.parent_age} yo.`,
561
- },
562
- }),
563
- },
564
- },
565
- },
566
- });
567
- validateNumberParams(
568
- {
569
- ...mockNumberInput,
570
- name: 'child_age',
571
- minimum: 5,
572
- maximum: 30,
573
- },
574
- fields[1]
575
- );
576
-
577
- expect(validateForm({ parent_age: 18, child_age: 4 })).toEqual({
578
- child_age: 'Must be greater or equal to 5', // applies the original error message
579
- });
580
- expect(validateForm({ parent_age: 18, child_age: 19 })).toEqual({
581
- child_age: 'The child cannot be older than the parent of 18 yo.', // applies the config.errorMessage
582
- });
583
- });
584
-
585
- it('overrides errorMessage conditionally', () => {
586
- const { fields, validateForm } = createScenario({
587
- schema: schemaBasic({
588
- newProperties: {
589
- parent_age: {
590
- maximum: 100,
591
- },
592
- child_age: {
593
- maximum: 40,
594
- errorMessage: {
595
- maximum: 'The child cannot be older than 40yo.',
596
- },
597
- },
598
- },
599
- }),
600
- config: {
601
- customProperties: {
602
- child_age: {
603
- minimum: (values, { maximum }) => {
604
- const minimumAge = values.parent_age / 2;
605
- if (
606
- maximum > minimumAge && // prevent invalid out-of-range maximum
607
- values.parent_age > values.child_age * 2 // parent is 2x as big as child age
608
- ) {
609
- return {
610
- minimum: minimumAge,
611
- errorMessage: {
612
- minimum: `The child cannot be younger than half of the parent. Must be at least ${minimumAge}yo.`,
613
- },
614
- };
615
- }
616
-
617
- return null;
618
- },
619
- },
620
- },
621
- },
622
- });
623
- validateNumberParams(
624
- {
625
- ...mockNumberInput,
626
- name: 'child_age',
627
- minimum: 5,
628
- maximum: 40,
629
- },
630
- fields[1]
631
- );
632
-
633
- // applies the errorMessage by default
634
- expect(validateForm({ parent_age: 50, child_age: 45 })).toEqual({
635
- child_age: 'The child cannot be older than 40yo.',
636
- });
637
- // applies the config.errorMessage if it's triggered
638
- expect(validateForm({ parent_age: 50, child_age: 10 })).toEqual({
639
- child_age: `The child cannot be younger than half of the parent. Must be at least 25yo.`,
640
- });
641
- });
642
- });
643
-
644
- describe('with x-jsf-errorMessage', () => {
645
- /* NOTE: We have 3 type of errors:
646
- - original error: (created by json-schema-form)
647
- - x-jsf-errorMessage: (declared on JSON Schema)
648
- - customValidation['x-jsf-errorMessage']: (declared on options)
649
- */
650
- it('overrides original error conditionally', () => {
651
- const { fields, validateForm } = createScenario({
652
- schema: schemaBasic(),
653
- config: {
654
- customProperties: {
655
- child_age: {
656
- maximum: (values, { maximum }) => ({
657
- maximum: values.parent_age || maximum,
658
- 'x-jsf-errorMessage': {
659
- maximum: `The child cannot be older than the parent of ${values.parent_age} yo.`,
660
- },
661
- }),
662
- },
663
- },
664
- },
665
- });
666
- validateNumberParams(
667
- {
668
- ...mockNumberInput,
669
- name: 'child_age',
670
- minimum: 5,
671
- maximum: 30,
672
- },
673
- fields[1]
674
- );
675
-
676
- expect(validateForm({ parent_age: 18, child_age: 4 })).toEqual({
677
- child_age: 'Must be greater or equal to 5', // applies the original error message
678
- });
679
- expect(validateForm({ parent_age: 18, child_age: 19 })).toEqual({
680
- child_age: 'The child cannot be older than the parent of 18 yo.', // applies the config.errorMessage
681
- });
682
- });
683
-
684
- it('overrides errorMessage conditionally', () => {
685
- const { fields, validateForm } = createScenario({
686
- schema: schemaBasic({
687
- newProperties: {
688
- parent_age: {
689
- maximum: 100,
690
- },
691
- child_age: {
692
- maximum: 40,
693
- 'x-jsf-errorMessage': {
694
- maximum: 'The child cannot be older than 40yo.',
695
- },
696
- },
697
- },
698
- }),
699
- config: {
700
- customProperties: {
701
- child_age: {
702
- minimum: (values, { maximum }) => {
703
- const minimumAge = values.parent_age / 2;
704
- if (
705
- maximum > minimumAge && // prevent invalid out-of-range maximum
706
- values.parent_age > values.child_age * 2 // parent is 2x as big as child age
707
- ) {
708
- return {
709
- minimum: minimumAge,
710
- 'x-jsf-errorMessage': {
711
- minimum: `The child cannot be younger than half of the parent. Must be at least ${minimumAge}yo.`,
712
- },
713
- };
714
- }
715
-
716
- return null;
717
- },
718
- },
719
- },
720
- },
721
- });
722
- validateNumberParams(
723
- {
724
- ...mockNumberInput,
725
- name: 'child_age',
726
- minimum: 5,
727
- maximum: 40,
728
- },
729
- fields[1]
730
- );
731
-
732
- // applies the errorMessage by default
733
- expect(validateForm({ parent_age: 50, child_age: 45 })).toEqual({
734
- child_age: 'The child cannot be older than 40yo.',
735
- });
736
- // applies the config.errorMessage if it's triggered
737
- expect(validateForm({ parent_age: 50, child_age: 10 })).toEqual({
738
- child_age: `The child cannot be younger than half of the parent. Must be at least 25yo.`,
739
- });
740
- });
741
- });
742
-
743
- describe('invalid validations', () => {
744
- it('outside the schema range logs warning', () => {
745
- const { fields, validateForm } = createScenario({
746
- schema: schemaBasic(),
747
- config: {
748
- customProperties: {
749
- parent_age: {
750
- minimum: 0,
751
- },
752
- },
753
- },
754
- });
755
-
756
- validateNumberParams(
757
- { ...mockNumberInput, minimum: 5, maximum: 100, name: 'parent_age' },
758
- fields[0]
759
- );
760
-
761
- // Keeps the default validation
762
- expect(validateForm({ parent_age: 0 })).toEqual({
763
- parent_age: 'Must be greater or equal to 5',
764
- });
765
-
766
- // eslint-disable-next-line no-console
767
- expect(console.warn).toHaveBeenNthCalledWith(
768
- 1,
769
- 'Custom validation for parent_age is not allowed because minimum:0 is less strict than the original range: 5 to 100'
770
- );
771
- // eslint-disable-next-line no-console
772
- console.warn.mockClear();
773
- });
774
-
775
- it('null or undefined ignores validation', () => {
776
- const { fields, validateForm } = createScenario({
777
- schema: schemaBasic(),
778
- config: {
779
- customProperties: {
780
- parent_age: {
781
- minimum: undefined,
782
- maximum: null,
783
- },
784
- },
785
- },
786
- });
787
-
788
- // The original validation is kept
789
- validateNumberParams(
790
- { ...mockNumberInput, minimum: 5, maximum: 100, name: 'parent_age' },
791
- fields[0]
792
- );
793
-
794
- expect(validateForm({ parent_age: 0 })).toEqual({
795
- parent_age: 'Must be greater or equal to 5',
796
- });
797
-
798
- expect(validateForm({ parent_age: 200 })).toEqual({
799
- parent_age: 'Must be smaller or equal to 100',
800
- });
801
- });
802
- });
803
- });