@remoteoss/json-schema-form 0.11.11-dev.20250220164730 → 1.0.0-alpha.1

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,787 +0,0 @@
1
- import { createHeadlessForm } from '@/createHeadlessForm';
2
-
3
- describe('Conditional attributes updated', () => {
4
- it('Should allow check of a nested property in a conditional', () => {
5
- const { handleValidation } = createHeadlessForm(
6
- {
7
- additionalProperties: false,
8
- allOf: [
9
- {
10
- if: {
11
- properties: {
12
- parent: {
13
- properties: {
14
- child: {
15
- const: 'yes',
16
- },
17
- },
18
- required: ['child'],
19
- },
20
- },
21
- required: ['parent'],
22
- },
23
- then: { required: ['parent_sibling'] },
24
- },
25
- ],
26
- properties: {
27
- parent: {
28
- additionalProperties: false,
29
- properties: {
30
- child: {
31
- oneOf: [{ const: 'yes' }, { const: 'no' }],
32
- type: 'string',
33
- },
34
- },
35
- required: ['child'],
36
- type: 'object',
37
- },
38
- parent_sibling: {
39
- type: 'integer',
40
- },
41
- },
42
- required: ['parent'],
43
- type: 'object',
44
- },
45
- { strictInputType: false }
46
- );
47
- expect(handleValidation({ parent: { child: 'no' } }).formErrors).toEqual(undefined);
48
- expect(handleValidation({ parent: { child: 'yes' } }).formErrors).toEqual({
49
- parent_sibling: 'Required field',
50
- });
51
- expect(handleValidation({ parent: { child: 'yes' }, parent_sibling: 1 }).formErrors).toEqual(
52
- undefined
53
- );
54
- });
55
-
56
- it('Update basic case with const, default, maximum', () => {
57
- const { fields, handleValidation } = createHeadlessForm(
58
- {
59
- properties: {
60
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
61
- hours: { type: 'number' },
62
- },
63
- allOf: [
64
- {
65
- if: {
66
- properties: { is_full_time: { const: 'yes' } },
67
- required: ['is_full_time'],
68
- },
69
- then: {
70
- properties: {
71
- hours: {
72
- const: 8,
73
- default: 8,
74
- },
75
- },
76
- },
77
- else: {
78
- properties: {
79
- hours: {
80
- maximum: 4,
81
- },
82
- },
83
- },
84
- },
85
- ],
86
- },
87
- { strictInputType: false }
88
- );
89
-
90
- // Given "Yes" it applies "const" and "default"
91
- expect(handleValidation({ is_full_time: 'yes', hours: 4 }).formErrors).toEqual({
92
- hours: 'The only accepted value is 8.',
93
- });
94
- expect(fields[1]).toMatchObject({ const: 8, default: 8 });
95
- expect(fields[1].maximum).toBeUndefined();
96
-
97
- // Changing to "No", applies the "maximum" and cleans "const" and "default"
98
- expect(handleValidation({ is_full_time: 'no', hours: 4 }).formErrors).toBeUndefined();
99
- expect(fields[1]).toMatchObject({ maximum: 4 });
100
- expect(fields[1].const).toBeUndefined();
101
- expect(fields[1].default).toBeUndefined();
102
-
103
- // Changing back to "Yes", it removes "maximum", and applies "const" and "default"
104
- expect(handleValidation({}).formErrors).toBeUndefined();
105
- expect(handleValidation({ is_full_time: 'yes', hours: 8 }).formErrors).toBeUndefined();
106
- expect(fields[1].maximum).toBeUndefined();
107
- expect(fields[1]).toMatchObject({ const: 8, default: 8 });
108
- });
109
-
110
- it('Update a new attribute (eg description)', () => {
111
- const { fields, handleValidation } = createHeadlessForm(
112
- {
113
- properties: {
114
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
115
- hours: {
116
- type: 'number',
117
- },
118
- },
119
- allOf: [
120
- {
121
- if: {
122
- properties: { is_full_time: { const: 'yes' } },
123
- required: ['is_full_time'],
124
- },
125
- then: {
126
- properties: {
127
- hours: {
128
- description: 'We recommend 8 hours.',
129
- },
130
- },
131
- },
132
- },
133
- ],
134
- },
135
- { strictInputType: false }
136
- );
137
-
138
- // By default the attribute is not set.
139
- expect(fields[1].description).toBeUndefined();
140
-
141
- // Given "Yes" it applies the conditional attribute
142
- expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
143
- expect(fields[1].description).toBe('We recommend 8 hours.');
144
-
145
- // Changing to "No", removes the description
146
- expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
147
- expect(fields[1].description).toBeUndefined();
148
-
149
- // Changing back to "Yes", it sets the attribute again
150
- expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
151
- expect(fields[1].description).toBe('We recommend 8 hours.');
152
- });
153
-
154
- it('Update an existing attribute (eg description)', () => {
155
- const { fields, handleValidation } = createHeadlessForm(
156
- {
157
- properties: {
158
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
159
- hours: {
160
- type: 'number',
161
- description: 'Any value works.',
162
- },
163
- },
164
- allOf: [
165
- {
166
- if: {
167
- properties: { is_full_time: { const: 'yes' } },
168
- required: ['is_full_time'],
169
- },
170
- then: {
171
- properties: {
172
- hours: {
173
- description: 'We recommend 8 hours.',
174
- },
175
- },
176
- },
177
- },
178
- ],
179
- },
180
- { strictInputType: false }
181
- );
182
-
183
- // By default the attribute is set the base value.
184
- expect(fields[1].description).toBe('Any value works.');
185
-
186
- // Given "Yes" it applies the conditional attribute
187
- expect(handleValidation({ is_full_time: 'yes', hours: 4 }).formErrors).toBeUndefined();
188
- expect(fields[1].description).toBe('We recommend 8 hours.');
189
-
190
- // Changing to "No", it applies the base value again.
191
- expect(handleValidation({ is_full_time: 'no', hours: 4 }).formErrors).toBeUndefined();
192
- expect(fields[1].description).toBe('Any value works.');
193
-
194
- // Changing back to "Yes", it sets the attribute again
195
- expect(handleValidation({ is_full_time: 'yes', hours: 8 }).formErrors).toBeUndefined();
196
- expect(fields[1].description).toBe('We recommend 8 hours.');
197
- });
198
-
199
- it('Update a nested attribute', () => {
200
- const { fields, handleValidation } = createHeadlessForm(
201
- {
202
- properties: {
203
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
204
- hours: {
205
- type: 'number',
206
- presentation: {
207
- inputType: 'number',
208
- anything: 'info',
209
- },
210
- },
211
- },
212
- allOf: [
213
- {
214
- if: {
215
- properties: { is_full_time: { const: 'yes' } },
216
- required: ['is_full_time'],
217
- },
218
- then: {
219
- properties: {
220
- hours: {
221
- presentation: {
222
- anything: 'danger',
223
- },
224
- },
225
- },
226
- },
227
- },
228
- ],
229
- },
230
- { strictInputType: false }
231
- );
232
-
233
- // By default the attribute is set the base value.
234
- expect(fields[1].anything).toBe('info');
235
-
236
- // Given "Yes" it applies the conditional attribute
237
- expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
238
- expect(fields[1].anything).toBe('danger');
239
-
240
- // Changing to "No", it applies the base value again.
241
- expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
242
- expect(fields[1].anything).toBe('info');
243
-
244
- // Changing back to "Yes", it sets the attribute again
245
- expect(handleValidation({ is_full_time: 'yes' }).formErrors).toBeUndefined();
246
- expect(fields[1].anything).toBe('danger');
247
- });
248
-
249
- it('Keeps existing attributes in matches that dont change the attr', () => {
250
- const { fields, handleValidation } = createHeadlessForm(
251
- {
252
- properties: {
253
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
254
- hours: {
255
- type: 'number',
256
- description: 'Any value works.',
257
- },
258
- },
259
- allOf: [
260
- {
261
- if: {
262
- properties: { is_full_time: { const: 'yes' } },
263
- required: ['is_full_time'],
264
- },
265
- then: {
266
- required: ['hours'],
267
- },
268
- else: {
269
- properties: {
270
- hours: false,
271
- },
272
- },
273
- },
274
- ],
275
- },
276
- { strictInputType: false }
277
- );
278
-
279
- // By default the attribute is set the base value, even though the field is invisible.
280
- expect(fields[1].description).toBe('Any value works.');
281
- expect(fields[1].isVisible).toBe(false);
282
-
283
- // Given "Yes" it keeps the base value
284
- expect(handleValidation({ is_full_time: 'yes' }).formErrors).toEqual({
285
- hours: 'Required field',
286
- });
287
- expect(fields[1].description).toBe('Any value works.');
288
- expect(fields[1].isVisible).toBe(true);
289
-
290
- // Changing to "No" it keeps the base value
291
- expect(handleValidation({ is_full_time: 'no' }).formErrors).toBeUndefined();
292
- expect(fields[1].description).toBe('Any value works.');
293
- expect(fields[1].isVisible).toBe(false);
294
- });
295
-
296
- it('Keeps internal attributes (dynamicInternalJsfAttrs)', () => {
297
- // This is necessary while we keep supporting "type", even if deprecated
298
- // otherwise our Remote app will break because it didn't migrate
299
- // from "type" to "inputType" yet.
300
- const { fields, handleValidation } = createHeadlessForm(
301
- {
302
- properties: {
303
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
304
- salary_period: {
305
- type: 'string',
306
- title: 'Salary period',
307
- oneOf: [
308
- { title: 'Weekly', const: 'weekly' },
309
- { title: 'Monthly', const: 'monthly' },
310
- ],
311
- },
312
- },
313
- allOf: [
314
- {
315
- if: {
316
- properties: { is_full_time: { const: 'yes' } },
317
- required: ['is_full_time'],
318
- },
319
- then: {
320
- properties: {
321
- salary_period: {
322
- description: 'We recommend montlhy.',
323
- },
324
- },
325
- },
326
- },
327
- ],
328
- },
329
- { strictInputType: false }
330
- );
331
-
332
- // Given "Yes" it keeps the "type"
333
- handleValidation({ is_full_time: 'yes' });
334
-
335
- // All the following attrs are never removed
336
- // during conditionals because they are core.
337
- expect(fields[1]).toMatchObject({
338
- name: 'salary_period',
339
- label: 'Salary period',
340
- required: false,
341
- type: 'radio',
342
- inputType: 'radio',
343
- jsonType: 'string',
344
- computedAttributes: {},
345
- calculateConditionalProperties: expect.any(Function),
346
- schema: expect.any(Object),
347
- scopedJsonSchema: expect.any(Object),
348
- isVisible: true,
349
- options: [
350
- {
351
- label: 'Weekly',
352
- value: 'weekly',
353
- },
354
- {
355
- label: 'Monthly',
356
- value: 'monthly',
357
- },
358
- ],
359
- });
360
- });
361
-
362
- it('Keeps custom attributes (dynamicInternalJsfAttrs) (hotfix temporary)', () => {
363
- // This is necessary as hotfix because we (Remote) use it internally.
364
- // Not cool, we'll need a better solution asap.
365
- const { fields, handleValidation } = createHeadlessForm(
366
- {
367
- properties: {
368
- is_full_time: { type: 'string', oneOf: [{ const: 'yes' }, { const: 'no' }] },
369
- salary_period: {
370
- type: 'string',
371
- title: 'Salary period',
372
- oneOf: [
373
- { title: 'Weekly', const: 'weekly' },
374
- { title: 'Monthly', const: 'monthly' },
375
- ],
376
- },
377
- },
378
- allOf: [
379
- {
380
- if: {
381
- properties: { is_full_time: { const: 'yes' } },
382
- required: ['is_full_time'],
383
- },
384
- then: {
385
- properties: {
386
- salary_period: {
387
- description: 'We recommend montlhy.',
388
- },
389
- },
390
- },
391
- },
392
- ],
393
- },
394
- {
395
- strictInputType: false,
396
- customProperties: {
397
- salary_period: {
398
- Component: '<A React Component>',
399
- calculateDynamicProperties: () => true,
400
- },
401
- },
402
- }
403
- );
404
-
405
- // It's there by default
406
- expect(fields[1].Component).toBe('<A React Component>');
407
- expect(fields[1].calculateDynamicProperties).toEqual(expect.any(Function));
408
-
409
- // Given "Yes", it stays there too.
410
- handleValidation({ is_full_time: 'yes' });
411
- expect(fields[1].Component).toBe('<A React Component>');
412
- expect(fields[1].calculateDynamicProperties).toEqual(expect.any(Function));
413
-
414
- // Given "No", it stays there too.
415
- handleValidation({ is_full_time: 'no' });
416
- expect(fields[1].Component).toBe('<A React Component>');
417
- expect(fields[1].calculateDynamicProperties).toEqual(expect.any(Function));
418
-
419
- expect(fields[1].visibilityCondition).toEqual(undefined);
420
- // visibilityCondition can be externally changed/updated/added, it stays there too;
421
- fields[1].visibilityCondition = () => false;
422
- handleValidation({ is_full_time: 'no' });
423
- expect(fields[1].visibilityCondition).toEqual(expect.any(Function));
424
- });
425
- });
426
-
427
- describe('Conditional with a minimum value check', () => {
428
- it('Should handle a maximum as a property field check', () => {
429
- const schema = {
430
- additionalProperties: false,
431
- allOf: [
432
- {
433
- if: {
434
- properties: {
435
- salary: {
436
- maximum: 119999,
437
- },
438
- },
439
- required: ['salary'],
440
- },
441
- then: {
442
- required: ['reason'],
443
- },
444
- else: {
445
- properties: {
446
- reason: false,
447
- },
448
- },
449
- },
450
- ],
451
- properties: {
452
- salary: {
453
- type: 'number',
454
- 'x-jsf-presentation': {
455
- inputType: 'money',
456
- },
457
- },
458
- reason: {
459
- oneOf: [
460
- {
461
- const: 'reason_one',
462
- },
463
- {
464
- const: 'reason_two',
465
- },
466
- ],
467
- type: 'string',
468
- },
469
- },
470
- required: ['salary'],
471
- type: 'object',
472
- };
473
-
474
- const { handleValidation } = createHeadlessForm(schema, { strictInputType: false });
475
- expect(handleValidation({ salary: 120000 }).formErrors).toEqual(undefined);
476
- expect(handleValidation({ salary: 1000 }).formErrors).toEqual({
477
- reason: 'Required field',
478
- });
479
- expect(handleValidation({ salary: 1000, reason: 'reason_one' }).formErrors).toEqual(undefined);
480
- });
481
- });
482
-
483
- describe('Conditional with literal booleans', () => {
484
- it('handles true case', () => {
485
- const schema = {
486
- properties: {
487
- is_full_time: {
488
- type: 'boolean',
489
- },
490
- salary: {
491
- type: 'number',
492
- },
493
- },
494
- required: [],
495
- if: true,
496
- then: {
497
- required: ['is_full_time'],
498
- },
499
- else: {
500
- required: ['salary'],
501
- },
502
- };
503
- const { fields, handleValidation } = createHeadlessForm(schema, { strictInputType: false });
504
-
505
- handleValidation({});
506
-
507
- expect(fields[0]).toMatchObject({
508
- name: 'is_full_time',
509
- required: true,
510
- });
511
- expect(fields[1]).toMatchObject({
512
- name: 'salary',
513
- required: false,
514
- });
515
- });
516
-
517
- it('handles false case', () => {
518
- const schema = {
519
- properties: {
520
- is_full_time: {
521
- type: 'boolean',
522
- },
523
- salary: {
524
- type: 'number',
525
- },
526
- },
527
- required: [],
528
- if: false,
529
- then: {
530
- required: ['is_full_time'],
531
- },
532
- else: {
533
- required: ['salary'],
534
- },
535
- };
536
- const { fields, handleValidation } = createHeadlessForm(schema, { strictInputType: false });
537
-
538
- handleValidation({});
539
-
540
- expect(fields[0]).toMatchObject({
541
- name: 'is_full_time',
542
- required: false,
543
- });
544
- expect(fields[1]).toMatchObject({
545
- name: 'salary',
546
- required: true,
547
- });
548
- });
549
- });
550
-
551
- describe('Conditional with anyOf', () => {
552
- it('handles true case', () => {
553
- const { fields, handleValidation } = createHeadlessForm(
554
- {
555
- additionalProperties: false,
556
- type: 'object',
557
- properties: {
558
- field_a: { type: 'string' },
559
- field_b: { type: 'string' },
560
- field_c: { type: 'string' },
561
- },
562
- allOf: [
563
- {
564
- if: {
565
- anyOf: [
566
- { properties: { field_a: { const: '1' } }, required: ['a'] },
567
- { properties: { field_b: { const: '2' } }, required: ['b'] },
568
- ],
569
- },
570
- then: {
571
- required: ['field_c'],
572
- },
573
- else: {
574
- properties: {
575
- field_c: false,
576
- },
577
- },
578
- },
579
- ],
580
- },
581
- { strictInputType: false }
582
- );
583
-
584
- expect(handleValidation({ field_a: '4', field_b: '2' }).formErrors).toEqual({
585
- field_c: 'Required field',
586
- });
587
- expect(fields[2].isVisible).toBe(true);
588
- });
589
-
590
- it('handles false case', () => {
591
- const { fields, handleValidation } = createHeadlessForm(
592
- {
593
- additionalProperties: false,
594
- type: 'object',
595
- properties: {
596
- field_a: { type: 'string' },
597
- field_b: { type: 'string' },
598
- field_c: { type: 'string' },
599
- },
600
- allOf: [
601
- {
602
- if: {
603
- anyOf: [
604
- { properties: { field_a: { const: '1' } }, required: ['a'] },
605
- { properties: { field_b: { const: '2' } }, required: ['b'] },
606
- ],
607
- },
608
- then: {
609
- required: ['field_c'],
610
- },
611
- else: {
612
- properties: {
613
- field_c: false,
614
- },
615
- },
616
- },
617
- ],
618
- },
619
- { strictInputType: false }
620
- );
621
-
622
- expect(handleValidation({ field_a: '4', field_b: '4' }).formErrors).toBeUndefined();
623
- expect(fields[2].isVisible).toBe(false);
624
- });
625
- });
626
-
627
- describe('Conditionals - bugs and code-smells', () => {
628
- // Why do we have these bugs?
629
- // To be honest we never realized it much later later.
630
- // We will fix them in the next major version.
631
-
632
- const schemaHasPet = {
633
- type: 'object',
634
- additionalProperties: false,
635
- properties: {
636
- has_pet: {
637
- title: 'Has Pet',
638
- description: 'Do you have a pet?',
639
- oneOf: [
640
- { title: 'Yes', const: 'yes' },
641
- { title: 'No', const: 'no' },
642
- ],
643
- type: 'string',
644
- },
645
- pet_name: {
646
- title: "Pet's name",
647
- type: 'string',
648
- },
649
- },
650
- required: ['has_pet'],
651
- allOf: [
652
- {
653
- if: {
654
- properties: {
655
- has_pet: { const: 'yes' },
656
- },
657
- required: ['has_pet'],
658
- },
659
- then: {
660
- required: ['pet_name'],
661
- },
662
- else: {
663
- properties: {
664
- pet_name: false,
665
- },
666
- },
667
- },
668
- ],
669
- };
670
-
671
- it('Given values from hidden fields, it does not thrown an error (@bug)', () => {
672
- const { fields, handleValidation } = createHeadlessForm(schemaHasPet, {
673
- strictInputType: false,
674
- });
675
-
676
- const petNameField = fields[1];
677
-
678
- const validation = handleValidation({ has_pet: 'no', pet_name: 'Max' });
679
- expect(petNameField.isVisible).toBe(false);
680
-
681
- // Bug: 🐛 It does not thrown an error,
682
- // but it should to be compliant with JSON Schema specs.
683
- expect(validation.formErrors).toBeUndefined();
684
- // The error should be something like:
685
- // expect(validation.formErrors).toEqual({ pet_name: 'Not allowed.'});
686
- });
687
-
688
- it('Given values from hidden fields, it mutates the values (@bug)', () => {
689
- const { handleValidation } = createHeadlessForm(schemaHasPet, {
690
- strictInputType: false,
691
- });
692
-
693
- const newValues = { has_pet: 'no', pet_name: 'Max' };
694
-
695
- const validation = handleValidation(newValues);
696
-
697
- expect(newValues).toEqual({
698
- has_pet: 'no',
699
- pet_name: null, // BUG! 🐛 Should still be "Max", should not be mutated.
700
- });
701
-
702
- // Same bug as explained in the previous test.
703
- expect(validation.formErrors).toBeUndefined();
704
- });
705
-
706
- it('Given multiple conditionals to the same field, it only applies the last one (@bug) - case 1', () => {
707
- const { handleValidation } = createHeadlessForm(
708
- {
709
- additionalProperties: false,
710
- properties: {
711
- field_a: { type: 'string' },
712
- field_b: { type: 'string' },
713
- field_c: { type: 'number' },
714
- },
715
- allOf: [
716
- {
717
- if: {
718
- properties: { field_a: { const: 'yes' } },
719
- required: ['field_a'],
720
- },
721
- then: { properties: { field_c: { minimum: 30 } } },
722
- },
723
- {
724
- if: {
725
- properties: { field_b: { const: 'yes' } },
726
- required: ['field_b'],
727
- },
728
- then: { properties: { field_c: { minimum: 10 } } },
729
- },
730
- ],
731
- },
732
- {
733
- strictInputType: false,
734
- }
735
- );
736
-
737
- const validation = handleValidation({ field_a: 'yes', field_b: 'yes', field_c: 5 });
738
- expect(validation.formErrors).toEqual({
739
- // BUG: 🐛 it should be "Must be greater or equal to 30"
740
- field_c: 'Must be greater or equal to 10',
741
- });
742
- });
743
-
744
- it('Given multiple conditionals to the same field, it only applies the last one (@bug) - case 2', () => {
745
- const { handleValidation } = createHeadlessForm(
746
- {
747
- additionalProperties: false,
748
- properties: {
749
- field_a: { type: 'string' },
750
- field_b: { type: 'string' },
751
- field_c: { type: 'number' },
752
- },
753
- allOf: [
754
- {
755
- if: {
756
- properties: { field_a: { const: 'yes' } },
757
- required: ['field_a'],
758
- },
759
- then: { properties: { field_c: { minimum: 5 } } },
760
- },
761
- {
762
- if: {
763
- properties: { field_b: { const: 'yes' } },
764
- required: ['field_b'],
765
- },
766
- then: { properties: { field_c: { maximum: 10 } } },
767
- },
768
- ],
769
- },
770
- {
771
- strictInputType: false,
772
- }
773
- );
774
-
775
- const validation1 = handleValidation({ field_a: 'yes', field_b: 'yes', field_c: 12 });
776
- expect(validation1.formErrors).toEqual({
777
- field_c: 'Must be smaller or equal to 10',
778
- });
779
-
780
- const validation2 = handleValidation({ field_a: 'yes', field_b: 'yes', field_c: 3 });
781
- // BUG: 🐛 it should be "Must be greater or equal to 5"
782
- expect(validation2.formErrors).toBeUndefined();
783
- // expect(validation1.formErrors).toEqual({
784
- // field_c: 'Must be greater or equal to 5',
785
- // });
786
- });
787
- });