@remoteoss/json-schema-form 0.4.0-beta.0 → 0.4.1-dev.20230702181843
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.
- package/dist/index.cjs +47 -19
- package/dist/index.js +47 -19
- package/dist/standalone.js +402 -802
- package/package.json +1 -1
- package/src/tests/createHeadlessForm.test.js +504 -266
- package/src/tests/helpers.js +177 -45
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
schemaInputTypeRadioDeprecated,
|
|
11
11
|
schemaInputTypeRadio,
|
|
12
12
|
schemaInputTypeRadioRequiredAndOptional,
|
|
13
|
+
schemaInputRadioOptionalNull,
|
|
14
|
+
schemaInputRadioOptionalConventional,
|
|
13
15
|
schemaInputTypeRadioOptionsWithDetails,
|
|
14
16
|
schemaInputTypeSelectSoloDeprecated,
|
|
15
17
|
schemaInputTypeSelectSolo,
|
|
@@ -45,6 +47,7 @@ import {
|
|
|
45
47
|
mockNestedFieldset,
|
|
46
48
|
mockGroupArrayInput,
|
|
47
49
|
schemaFieldsetScopedCondition,
|
|
50
|
+
schemaWithConditionalToFieldset,
|
|
48
51
|
schemaWithConditionalPresentationProperties,
|
|
49
52
|
schemaWithConditionalReadOnlyProperty,
|
|
50
53
|
schemaWithWrongConditional,
|
|
@@ -77,6 +80,17 @@ function friendlyError({ formErrors }) {
|
|
|
77
80
|
return formErrors;
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
// Get a field by name recursively
|
|
84
|
+
// eg getField(demo, "age") -> returns "age" field
|
|
85
|
+
// eg getField(demo, child, name) -> returns "child.name" subfield
|
|
86
|
+
const getField = (fields, name, ...subNames) => {
|
|
87
|
+
const field = fields.find((f) => f.name === name);
|
|
88
|
+
if (subNames.length > 0) {
|
|
89
|
+
return getField(field.fields, ...subNames);
|
|
90
|
+
}
|
|
91
|
+
return field;
|
|
92
|
+
};
|
|
93
|
+
|
|
80
94
|
beforeEach(() => {
|
|
81
95
|
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
82
96
|
});
|
|
@@ -420,6 +434,42 @@ describe('createHeadlessForm', () => {
|
|
|
420
434
|
});
|
|
421
435
|
|
|
422
436
|
describe('field support', () => {
|
|
437
|
+
function assertOptionsAllowed({ handleValidation, fieldName, validOptions }) {
|
|
438
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
439
|
+
|
|
440
|
+
// All allowed options are valid
|
|
441
|
+
validOptions.forEach((value) => {
|
|
442
|
+
expect(
|
|
443
|
+
validateForm({
|
|
444
|
+
[fieldName]: value,
|
|
445
|
+
})
|
|
446
|
+
).toBeUndefined();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Any other arbitrary value is not valid.
|
|
450
|
+
expect(
|
|
451
|
+
validateForm({
|
|
452
|
+
[fieldName]: 'blah-blah',
|
|
453
|
+
})
|
|
454
|
+
).toEqual({
|
|
455
|
+
[fieldName]: 'The option "blah-blah" is not valid.',
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// This is a required field:
|
|
459
|
+
expect(validateForm({})).toEqual({
|
|
460
|
+
[fieldName]: 'Required field',
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// As required field, empty string ("") is also considered empty. @BUG RMT-518
|
|
464
|
+
expect(
|
|
465
|
+
validateForm({
|
|
466
|
+
[fieldName]: '',
|
|
467
|
+
})
|
|
468
|
+
).toEqual({
|
|
469
|
+
[fieldName]: 'Required field',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
423
473
|
it('support "text" field type', () => {
|
|
424
474
|
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
425
475
|
|
|
@@ -522,38 +572,43 @@ describe('createHeadlessForm', () => {
|
|
|
522
572
|
});
|
|
523
573
|
|
|
524
574
|
it('support "select" field type @deprecated', () => {
|
|
525
|
-
const
|
|
575
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
|
|
526
576
|
|
|
527
|
-
expect(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
577
|
+
expect(fields).toMatchObject([
|
|
578
|
+
{
|
|
579
|
+
description: 'Life Insurance',
|
|
580
|
+
label: 'Benefits (solo)',
|
|
581
|
+
name: 'benefits',
|
|
582
|
+
placeholder: 'Select...',
|
|
583
|
+
type: 'select',
|
|
584
|
+
options: [
|
|
585
|
+
{
|
|
586
|
+
label: 'Medical Insurance',
|
|
587
|
+
value: 'Medical Insurance',
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
label: 'Health Insurance',
|
|
591
|
+
value: 'Health Insurance',
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
label: 'Travel Bonus',
|
|
595
|
+
value: 'Travel Bonus',
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
},
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
assertOptionsAllowed({
|
|
602
|
+
handleValidation,
|
|
603
|
+
fieldName: 'benefits',
|
|
604
|
+
validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
|
|
551
605
|
});
|
|
552
606
|
});
|
|
607
|
+
|
|
553
608
|
it('support "select" field type', () => {
|
|
554
|
-
const
|
|
609
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
555
610
|
|
|
556
|
-
const fieldSelect =
|
|
611
|
+
const fieldSelect = fields[0];
|
|
557
612
|
expect(fieldSelect).toMatchObject({
|
|
558
613
|
name: 'browsers',
|
|
559
614
|
label: 'Browsers (solo)',
|
|
@@ -576,6 +631,12 @@ describe('createHeadlessForm', () => {
|
|
|
576
631
|
});
|
|
577
632
|
|
|
578
633
|
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
634
|
+
|
|
635
|
+
assertOptionsAllowed({
|
|
636
|
+
handleValidation,
|
|
637
|
+
fieldName: 'browsers',
|
|
638
|
+
validOptions: ['chr', 'ff', 'ie'],
|
|
639
|
+
});
|
|
579
640
|
});
|
|
580
641
|
|
|
581
642
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
@@ -666,96 +727,149 @@ describe('createHeadlessForm', () => {
|
|
|
666
727
|
});
|
|
667
728
|
|
|
668
729
|
it('support "radio" field type @deprecated', () => {
|
|
669
|
-
const
|
|
730
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
670
731
|
|
|
671
|
-
expect(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
],
|
|
692
|
-
});
|
|
732
|
+
expect(fields).toMatchObject([
|
|
733
|
+
{
|
|
734
|
+
description: 'Do you have any siblings?',
|
|
735
|
+
label: 'Has siblings',
|
|
736
|
+
name: 'has_siblings',
|
|
737
|
+
options: [
|
|
738
|
+
{
|
|
739
|
+
label: 'Yes',
|
|
740
|
+
value: 'yes',
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
label: 'No',
|
|
744
|
+
value: 'no',
|
|
745
|
+
},
|
|
746
|
+
],
|
|
747
|
+
required: true,
|
|
748
|
+
schema: expect.any(Object),
|
|
749
|
+
type: 'radio',
|
|
750
|
+
},
|
|
751
|
+
]);
|
|
693
752
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
753
|
+
assertOptionsAllowed({
|
|
754
|
+
handleValidation,
|
|
755
|
+
fieldName: 'has_siblings',
|
|
756
|
+
validOptions: ['yes', 'no'],
|
|
757
|
+
});
|
|
697
758
|
});
|
|
698
759
|
it('support "radio" field type', () => {
|
|
699
|
-
const
|
|
760
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
|
|
700
761
|
|
|
701
|
-
expect(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
],
|
|
722
|
-
});
|
|
762
|
+
expect(fields).toMatchObject([
|
|
763
|
+
{
|
|
764
|
+
description: 'Do you have any siblings?',
|
|
765
|
+
label: 'Has siblings',
|
|
766
|
+
name: 'has_siblings',
|
|
767
|
+
options: [
|
|
768
|
+
{
|
|
769
|
+
label: 'Yes',
|
|
770
|
+
value: 'yes',
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
label: 'No',
|
|
774
|
+
value: 'no',
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
required: true,
|
|
778
|
+
schema: expect.any(Object),
|
|
779
|
+
type: 'radio',
|
|
780
|
+
},
|
|
781
|
+
]);
|
|
723
782
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
783
|
+
assertOptionsAllowed({
|
|
784
|
+
handleValidation,
|
|
785
|
+
fieldName: 'has_siblings',
|
|
786
|
+
validOptions: ['yes', 'no'],
|
|
787
|
+
});
|
|
727
788
|
});
|
|
728
789
|
|
|
729
790
|
it('support "radio" optional field', () => {
|
|
730
|
-
const
|
|
791
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
792
|
+
schemaInputTypeRadioRequiredAndOptional
|
|
793
|
+
);
|
|
794
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
731
795
|
|
|
732
|
-
expect(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
796
|
+
expect(fields).toMatchObject([
|
|
797
|
+
{},
|
|
798
|
+
{
|
|
799
|
+
name: 'has_car',
|
|
800
|
+
label: 'Has car',
|
|
801
|
+
description: 'Do you have a car? (optional field, check oneOf)',
|
|
802
|
+
options: [
|
|
803
|
+
{
|
|
804
|
+
label: 'Yes',
|
|
805
|
+
value: 'yes',
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
label: 'No',
|
|
809
|
+
value: 'no',
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
required: false,
|
|
813
|
+
schema: expect.any(Object),
|
|
814
|
+
type: 'radio',
|
|
815
|
+
},
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
expect(
|
|
819
|
+
validateForm({
|
|
820
|
+
has_siblings: 'yes',
|
|
821
|
+
has_car: 'yes',
|
|
822
|
+
})
|
|
823
|
+
).toBeUndefined();
|
|
824
|
+
expect(validateForm({})).toEqual({
|
|
825
|
+
has_siblings: 'Required field',
|
|
754
826
|
});
|
|
827
|
+
});
|
|
755
828
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
829
|
+
describe('support "radio" optional field - more examples @BUG RMT-518', () => {
|
|
830
|
+
function assertCommonBehavior(validateForm) {
|
|
831
|
+
// Happy path
|
|
832
|
+
expect(validateForm({ has_car: 'yes' })).toBeUndefined();
|
|
833
|
+
|
|
834
|
+
// Accepts undefined field
|
|
835
|
+
expect(validateForm({})).toBeUndefined();
|
|
836
|
+
|
|
837
|
+
// Does not accept other values
|
|
838
|
+
expect(validateForm({ has_car: 'blah-blah' })).toEqual({
|
|
839
|
+
has_car: 'The option "blah-blah" is not valid.',
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Does not accept "null" as string
|
|
843
|
+
expect(validateForm({ has_car: 'null' })).toEqual({
|
|
844
|
+
has_car: 'The option "null" is not valid.',
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Accepts empty string ("") — @BUG RMT-518
|
|
848
|
+
// Expectation: Does not accept empty string ("")
|
|
849
|
+
expect(validateForm({ has_car: '' })).toBeUndefined();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
it('with null option (as Remote)', () => {
|
|
853
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
|
|
854
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
855
|
+
|
|
856
|
+
assertCommonBehavior(validateForm);
|
|
857
|
+
|
|
858
|
+
// Accepts null value
|
|
859
|
+
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('without null option (conventional way)', () => {
|
|
863
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
|
|
864
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
865
|
+
|
|
866
|
+
assertCommonBehavior(validateForm);
|
|
867
|
+
|
|
868
|
+
// Does NOT accept null value
|
|
869
|
+
expect(validateForm({ has_car: null })).toEqual({
|
|
870
|
+
has_car: 'The option null is not valid.',
|
|
871
|
+
});
|
|
872
|
+
});
|
|
759
873
|
});
|
|
760
874
|
|
|
761
875
|
it('support "radio" field type with extra info inside each option', () => {
|
|
@@ -1182,46 +1296,6 @@ describe('createHeadlessForm', () => {
|
|
|
1182
1296
|
});
|
|
1183
1297
|
});
|
|
1184
1298
|
|
|
1185
|
-
it('supports "fieldset" field type', () => {
|
|
1186
|
-
const result = createHeadlessForm(
|
|
1187
|
-
JSONSchemaBuilder()
|
|
1188
|
-
.addInput({
|
|
1189
|
-
fieldset: mockFieldset,
|
|
1190
|
-
})
|
|
1191
|
-
.build()
|
|
1192
|
-
);
|
|
1193
|
-
|
|
1194
|
-
expect(result).toMatchObject({
|
|
1195
|
-
fields: [
|
|
1196
|
-
{
|
|
1197
|
-
description: 'Fieldset description',
|
|
1198
|
-
label: 'Fieldset title',
|
|
1199
|
-
name: 'fieldset',
|
|
1200
|
-
type: 'fieldset',
|
|
1201
|
-
required: false,
|
|
1202
|
-
fields: [
|
|
1203
|
-
{
|
|
1204
|
-
description: 'The number of your national identification (max 10 digits)',
|
|
1205
|
-
label: 'ID number',
|
|
1206
|
-
name: 'id_number',
|
|
1207
|
-
type: 'text',
|
|
1208
|
-
required: true,
|
|
1209
|
-
},
|
|
1210
|
-
{
|
|
1211
|
-
description: 'How many open tabs do you have?',
|
|
1212
|
-
label: 'Tabs',
|
|
1213
|
-
maximum: 10,
|
|
1214
|
-
minimum: 1,
|
|
1215
|
-
name: 'tabs',
|
|
1216
|
-
type: 'number',
|
|
1217
|
-
required: false,
|
|
1218
|
-
},
|
|
1219
|
-
],
|
|
1220
|
-
},
|
|
1221
|
-
],
|
|
1222
|
-
});
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1225
1299
|
it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
|
|
1226
1300
|
const result = createHeadlessForm(
|
|
1227
1301
|
JSONSchemaBuilder()
|
|
@@ -1287,130 +1361,298 @@ describe('createHeadlessForm', () => {
|
|
|
1287
1361
|
});
|
|
1288
1362
|
});
|
|
1289
1363
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
})
|
|
1296
|
-
.build()
|
|
1297
|
-
);
|
|
1298
|
-
|
|
1299
|
-
expect(result).toMatchObject({
|
|
1300
|
-
fields: [
|
|
1301
|
-
{
|
|
1302
|
-
label: 'Nested fieldset title',
|
|
1303
|
-
description: 'Nested fieldset description',
|
|
1304
|
-
name: 'nestedFieldset',
|
|
1305
|
-
type: 'fieldset',
|
|
1306
|
-
required: false,
|
|
1307
|
-
fields: [
|
|
1308
|
-
{
|
|
1309
|
-
description: 'Fieldset description',
|
|
1310
|
-
label: 'Fieldset title',
|
|
1311
|
-
name: 'innerFieldset',
|
|
1312
|
-
type: 'fieldset',
|
|
1313
|
-
required: false,
|
|
1314
|
-
fields: [
|
|
1315
|
-
{
|
|
1316
|
-
description: 'The number of your national identification (max 10 digits)',
|
|
1317
|
-
label: 'ID number',
|
|
1318
|
-
name: 'id_number',
|
|
1319
|
-
type: 'text',
|
|
1320
|
-
required: true,
|
|
1321
|
-
},
|
|
1322
|
-
{
|
|
1323
|
-
description: 'How many open tabs do you have?',
|
|
1324
|
-
label: 'Tabs',
|
|
1325
|
-
maximum: 10,
|
|
1326
|
-
minimum: 1,
|
|
1327
|
-
name: 'tabs',
|
|
1328
|
-
type: 'number',
|
|
1329
|
-
required: false,
|
|
1330
|
-
},
|
|
1331
|
-
],
|
|
1332
|
-
},
|
|
1333
|
-
],
|
|
1364
|
+
describe('supports "fieldset" field type', () => {
|
|
1365
|
+
it('supports basic case', () => {
|
|
1366
|
+
const result = createHeadlessForm({
|
|
1367
|
+
properties: {
|
|
1368
|
+
fieldset: mockFieldset,
|
|
1334
1369
|
},
|
|
1335
|
-
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
expect(result).toMatchObject({
|
|
1373
|
+
fields: [
|
|
1374
|
+
{
|
|
1375
|
+
description: 'Fieldset description',
|
|
1376
|
+
label: 'Fieldset title',
|
|
1377
|
+
name: 'fieldset',
|
|
1378
|
+
type: 'fieldset',
|
|
1379
|
+
required: false,
|
|
1380
|
+
fields: [
|
|
1381
|
+
{
|
|
1382
|
+
description: 'The number of your national identification (max 10 digits)',
|
|
1383
|
+
label: 'ID number',
|
|
1384
|
+
name: 'id_number',
|
|
1385
|
+
type: 'text',
|
|
1386
|
+
required: true,
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
description: 'How many open tabs do you have?',
|
|
1390
|
+
label: 'Tabs',
|
|
1391
|
+
maximum: 10,
|
|
1392
|
+
minimum: 1,
|
|
1393
|
+
name: 'tabs',
|
|
1394
|
+
type: 'number',
|
|
1395
|
+
required: false,
|
|
1396
|
+
},
|
|
1397
|
+
],
|
|
1398
|
+
},
|
|
1399
|
+
],
|
|
1400
|
+
});
|
|
1336
1401
|
});
|
|
1337
|
-
});
|
|
1338
1402
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1403
|
+
it('supports nested fieldset (fieldset inside fieldset)', () => {
|
|
1404
|
+
const result = createHeadlessForm(
|
|
1405
|
+
JSONSchemaBuilder()
|
|
1406
|
+
.addInput({
|
|
1407
|
+
nestedFieldset: mockNestedFieldset,
|
|
1408
|
+
})
|
|
1409
|
+
.build()
|
|
1410
|
+
);
|
|
1342
1411
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1412
|
+
expect(result).toMatchObject({
|
|
1413
|
+
fields: [
|
|
1414
|
+
{
|
|
1415
|
+
label: 'Nested fieldset title',
|
|
1416
|
+
description: 'Nested fieldset description',
|
|
1417
|
+
name: 'nestedFieldset',
|
|
1418
|
+
type: 'fieldset',
|
|
1419
|
+
required: false,
|
|
1420
|
+
fields: [
|
|
1421
|
+
{
|
|
1422
|
+
description: 'Fieldset description',
|
|
1423
|
+
label: 'Fieldset title',
|
|
1424
|
+
name: 'innerFieldset',
|
|
1425
|
+
type: 'fieldset',
|
|
1426
|
+
required: false,
|
|
1427
|
+
fields: [
|
|
1428
|
+
{
|
|
1429
|
+
description: 'The number of your national identification (max 10 digits)',
|
|
1430
|
+
label: 'ID number',
|
|
1431
|
+
name: 'id_number',
|
|
1432
|
+
type: 'text',
|
|
1433
|
+
required: true,
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
description: 'How many open tabs do you have?',
|
|
1437
|
+
label: 'Tabs',
|
|
1438
|
+
maximum: 10,
|
|
1439
|
+
minimum: 1,
|
|
1440
|
+
name: 'tabs',
|
|
1441
|
+
type: 'number',
|
|
1442
|
+
required: false,
|
|
1443
|
+
},
|
|
1444
|
+
],
|
|
1445
|
+
},
|
|
1446
|
+
],
|
|
1447
|
+
},
|
|
1448
|
+
],
|
|
1449
|
+
});
|
|
1348
1450
|
});
|
|
1349
1451
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
validateForm(
|
|
1452
|
+
it('supported "fieldset" with scoped conditionals', () => {
|
|
1453
|
+
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
1454
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1455
|
+
|
|
1456
|
+
// The "child.has_child" is required
|
|
1457
|
+
expect(validateForm({})).toEqual({
|
|
1353
1458
|
child: {
|
|
1354
|
-
has_child: '
|
|
1459
|
+
has_child: 'Required field',
|
|
1355
1460
|
},
|
|
1356
|
-
})
|
|
1357
|
-
).toBeUndefined();
|
|
1461
|
+
});
|
|
1358
1462
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1463
|
+
// The "child.no" is valid
|
|
1464
|
+
expect(
|
|
1465
|
+
validateForm({
|
|
1466
|
+
child: {
|
|
1467
|
+
has_child: 'no',
|
|
1468
|
+
},
|
|
1469
|
+
})
|
|
1470
|
+
).toBeUndefined();
|
|
1471
|
+
|
|
1472
|
+
// Invalid because it expect child.age too
|
|
1473
|
+
expect(
|
|
1474
|
+
validateForm({
|
|
1475
|
+
child: {
|
|
1476
|
+
has_child: 'yes',
|
|
1477
|
+
},
|
|
1478
|
+
})
|
|
1479
|
+
).toEqual({
|
|
1362
1480
|
child: {
|
|
1363
|
-
|
|
1481
|
+
age: 'Required field',
|
|
1364
1482
|
},
|
|
1365
|
-
})
|
|
1366
|
-
|
|
1367
|
-
child
|
|
1368
|
-
|
|
1369
|
-
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
// Valid without optional child.passport_id
|
|
1486
|
+
expect(
|
|
1487
|
+
validateForm({
|
|
1488
|
+
child: {
|
|
1489
|
+
has_child: 'yes',
|
|
1490
|
+
age: 15,
|
|
1491
|
+
},
|
|
1492
|
+
})
|
|
1493
|
+
).toBeUndefined();
|
|
1494
|
+
|
|
1495
|
+
// Valid with optional child.passport_id
|
|
1496
|
+
expect(
|
|
1497
|
+
validateForm({
|
|
1498
|
+
child: {
|
|
1499
|
+
has_child: 'yes',
|
|
1500
|
+
age: 15,
|
|
1501
|
+
passport_id: 'asdf',
|
|
1502
|
+
},
|
|
1503
|
+
})
|
|
1504
|
+
).toBeUndefined();
|
|
1370
1505
|
});
|
|
1371
1506
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
validateForm(
|
|
1375
|
-
child: {
|
|
1376
|
-
has_child: 'yes',
|
|
1377
|
-
age: 15,
|
|
1378
|
-
},
|
|
1379
|
-
})
|
|
1380
|
-
).toBeUndefined();
|
|
1507
|
+
it('should set any nested "fieldset" form values to null when they are invisible', async () => {
|
|
1508
|
+
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
1509
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1381
1510
|
|
|
1382
|
-
|
|
1383
|
-
expect(
|
|
1384
|
-
validateForm({
|
|
1511
|
+
const formValues = {
|
|
1385
1512
|
child: {
|
|
1386
1513
|
has_child: 'yes',
|
|
1387
1514
|
age: 15,
|
|
1388
|
-
passport_id: 'asdf',
|
|
1389
1515
|
},
|
|
1390
|
-
}
|
|
1391
|
-
).toBeUndefined();
|
|
1392
|
-
});
|
|
1516
|
+
};
|
|
1393
1517
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1518
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1519
|
+
expect(formValues.child.age).toBe(15);
|
|
1397
1520
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1521
|
+
formValues.child.has_child = 'no';
|
|
1522
|
+
// form value updates re-validate; see computeYupSchema()
|
|
1523
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1524
|
+
|
|
1525
|
+
// when child.has_child is 'no' child.age is invisible
|
|
1526
|
+
expect(formValues.child.age).toBe(null);
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
describe('supports conditionals to fieldsets', () => {
|
|
1530
|
+
// To not mix the concepts:
|
|
1531
|
+
// - Scoped conditionals: Conditionals written inside a fieldset
|
|
1532
|
+
// - Conditionals to fieldsets: Root conditionals that affect a fieldset
|
|
1533
|
+
|
|
1534
|
+
// This describe has sequential tests, covering the following:
|
|
1535
|
+
// If the working_hours > 30,
|
|
1536
|
+
// Then the fieldset perks.food changes (the "no" option gets removed)
|
|
1537
|
+
|
|
1538
|
+
// Setup (arrange)
|
|
1539
|
+
const { fields, handleValidation } = createHeadlessForm(schemaWithConditionalToFieldset);
|
|
1540
|
+
|
|
1541
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1542
|
+
const originalFood = getField(fields, 'perks', 'food');
|
|
1543
|
+
|
|
1544
|
+
const perksForLowWorkHours = {
|
|
1545
|
+
food: 'no', // this option will be removed when the condition happens.
|
|
1546
|
+
retirement: 'basic',
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
it('by default, the Perks.food has 4 options', () => {
|
|
1550
|
+
expect(originalFood.options).toHaveLength(4);
|
|
1551
|
+
expect(originalFood.description).toBeUndefined();
|
|
1552
|
+
|
|
1553
|
+
// Ensure the perks are required
|
|
1554
|
+
expect(validateForm({})).toEqual({
|
|
1555
|
+
work_hours_per_week: 'Required field',
|
|
1556
|
+
perks: {
|
|
1557
|
+
food: 'Required field',
|
|
1558
|
+
retirement: 'Required field',
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
// Given low work hours, the form is valid.
|
|
1563
|
+
expect(
|
|
1564
|
+
validateForm({
|
|
1565
|
+
work_hours_per_week: 5,
|
|
1566
|
+
perks: perksForLowWorkHours,
|
|
1567
|
+
})
|
|
1568
|
+
).toBeUndefined();
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
it('Given a lot work hours, the perks.food options change', () => {
|
|
1572
|
+
expect(
|
|
1573
|
+
validateForm({
|
|
1574
|
+
work_hours_per_week: 35,
|
|
1575
|
+
})
|
|
1576
|
+
).toEqual({
|
|
1577
|
+
pto: 'Required field', // Sanity-check - this field gets required too.
|
|
1578
|
+
perks: {
|
|
1579
|
+
food: 'Required field',
|
|
1580
|
+
retirement: 'Required field',
|
|
1581
|
+
},
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
// The fieldset changed!
|
|
1585
|
+
const foodField = getField(fields, 'perks', 'food');
|
|
1586
|
+
// perks.food options changed ("No" was removed)
|
|
1587
|
+
expect(foodField.options).toHaveLength(3);
|
|
1588
|
+
|
|
1589
|
+
// Ensure the "no" option is no longer accepted:
|
|
1590
|
+
// This is a very important test in case the UI fails for some reason.
|
|
1591
|
+
expect(
|
|
1592
|
+
validateForm({
|
|
1593
|
+
work_hours_per_week: 35,
|
|
1594
|
+
pto: 20,
|
|
1595
|
+
perks: perksForLowWorkHours,
|
|
1596
|
+
})
|
|
1597
|
+
).toEqual({
|
|
1598
|
+
perks: {
|
|
1599
|
+
food: 'The option "no" is not valid.',
|
|
1600
|
+
},
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
// perks.food has a new description
|
|
1604
|
+
expect(foodField.description).toBe("Above 30 hours, the 'no' option disappears.");
|
|
1605
|
+
// pto has a new description
|
|
1606
|
+
expect(getField(fields, 'pto').description).toBe(
|
|
1607
|
+
'Above 30 hours, the PTO needs to be at least 20 days.'
|
|
1608
|
+
);
|
|
1609
|
+
|
|
1610
|
+
// Sanity-check: Now the PTO also has a minimum value
|
|
1611
|
+
expect(
|
|
1612
|
+
validateForm({
|
|
1613
|
+
work_hours_per_week: 35,
|
|
1614
|
+
pto: 5, // too low
|
|
1615
|
+
perks: { food: 'lunch', retirement: 'basic' },
|
|
1616
|
+
})
|
|
1617
|
+
).toEqual({
|
|
1618
|
+
pto: 'Must be greater or equal to 20',
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
it('When changing back to low work hours, the perks.food goes back to the original state', () => {
|
|
1623
|
+
expect(
|
|
1624
|
+
validateForm({
|
|
1625
|
+
work_hours_per_week: 10,
|
|
1626
|
+
pto: 5,
|
|
1627
|
+
})
|
|
1628
|
+
).toEqual({
|
|
1629
|
+
perks: {
|
|
1630
|
+
food: 'Required field',
|
|
1631
|
+
retirement: 'Required field',
|
|
1632
|
+
},
|
|
1633
|
+
// ...pto is minimum error is gone! (sanity-check)
|
|
1634
|
+
});
|
|
1404
1635
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1636
|
+
const foodField = getField(fields, 'perks', 'food');
|
|
1637
|
+
// ...Number of perks.food options was back to the original (4)
|
|
1638
|
+
expect(foodField.options).toHaveLength(4);
|
|
1639
|
+
// ...Food description was back to the original
|
|
1640
|
+
expect(foodField.description).toBeUndefined();
|
|
1407
1641
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1642
|
+
// @BUG RMT-58 PTO description should disappear, but it didn't.
|
|
1643
|
+
expect(getField(fields, 'pto').description).toBe(
|
|
1644
|
+
'Above 30 hours, the PTO needs to be at least 20 days.'
|
|
1645
|
+
);
|
|
1411
1646
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1647
|
+
// Given again "low perks", the form valid.
|
|
1648
|
+
expect(
|
|
1649
|
+
validateForm({
|
|
1650
|
+
work_hours_per_week: 10,
|
|
1651
|
+
perks: perksForLowWorkHours,
|
|
1652
|
+
})
|
|
1653
|
+
).toBeUndefined();
|
|
1654
|
+
});
|
|
1655
|
+
});
|
|
1414
1656
|
});
|
|
1415
1657
|
|
|
1416
1658
|
it('support "email" field type', () => {
|
|
@@ -1729,14 +1971,12 @@ describe('createHeadlessForm', () => {
|
|
|
1729
1971
|
strictInputType: false,
|
|
1730
1972
|
});
|
|
1731
1973
|
|
|
1732
|
-
const
|
|
1733
|
-
|
|
1734
|
-
const aFieldInRoot = getByName(fields, 'a_string');
|
|
1974
|
+
const aFieldInRoot = getField(fields, 'a_string');
|
|
1735
1975
|
// It's the entire json schema
|
|
1736
1976
|
expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
|
|
1737
1977
|
|
|
1738
|
-
const aFieldset =
|
|
1739
|
-
const aFieldInTheFieldset =
|
|
1978
|
+
const aFieldset = getField(fields, 'a_object');
|
|
1979
|
+
const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
|
|
1740
1980
|
|
|
1741
1981
|
// It's only the json schema of that fieldset
|
|
1742
1982
|
expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
|
|
@@ -2196,8 +2436,6 @@ describe('createHeadlessForm', () => {
|
|
|
2196
2436
|
});
|
|
2197
2437
|
|
|
2198
2438
|
describe('when a JSON Schema is provided', () => {
|
|
2199
|
-
const getByName = (fields, name) => fields.find((f) => f.name === name);
|
|
2200
|
-
|
|
2201
2439
|
describe('and all fields are optional', () => {
|
|
2202
2440
|
let handleValidation;
|
|
2203
2441
|
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
@@ -2604,23 +2842,23 @@ describe('createHeadlessForm', () => {
|
|
|
2604
2842
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2605
2843
|
field_a: null,
|
|
2606
2844
|
});
|
|
2607
|
-
expect(
|
|
2845
|
+
expect(getField(fields, 'field_b').isVisible).toBe(false);
|
|
2608
2846
|
});
|
|
2609
2847
|
|
|
2610
2848
|
it('given a match, runs "then" (turns visible and editable)', () => {
|
|
2611
2849
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2612
2850
|
initialValues: { field_a: 'yes' },
|
|
2613
2851
|
});
|
|
2614
|
-
expect(
|
|
2615
|
-
expect(
|
|
2852
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2853
|
+
expect(getField(fields, 'field_b').readOnly).toBe(false);
|
|
2616
2854
|
});
|
|
2617
2855
|
|
|
2618
2856
|
it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
|
|
2619
2857
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2620
2858
|
initialValues: { field_a: 'no' },
|
|
2621
2859
|
});
|
|
2622
|
-
expect(
|
|
2623
|
-
expect(
|
|
2860
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2861
|
+
expect(getField(fields, 'field_b').readOnly).toBe(true);
|
|
2624
2862
|
});
|
|
2625
2863
|
});
|
|
2626
2864
|
|
|
@@ -2632,26 +2870,26 @@ describe('createHeadlessForm', () => {
|
|
|
2632
2870
|
initialValues: { field_a: null, field_a_wrong: null },
|
|
2633
2871
|
});
|
|
2634
2872
|
// The dependent correct field gets hidden, but...
|
|
2635
|
-
expect(
|
|
2873
|
+
expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
|
|
2636
2874
|
// ...the dependent wrong field stays visible because the
|
|
2637
2875
|
// conditional is wrong (it's missing the if.required[])
|
|
2638
|
-
expect(
|
|
2876
|
+
expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
|
|
2639
2877
|
});
|
|
2640
2878
|
|
|
2641
2879
|
it('given a match ("yes"), both runs "then" (turn visible)', () => {
|
|
2642
2880
|
const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2643
2881
|
initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
|
|
2644
2882
|
});
|
|
2645
|
-
expect(
|
|
2646
|
-
expect(
|
|
2883
|
+
expect(getField(fieldsVisible, 'field_b').isVisible).toBe(true);
|
|
2884
|
+
expect(getField(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
|
|
2647
2885
|
});
|
|
2648
2886
|
|
|
2649
2887
|
it('not given a match ("no"), both run else (stay hidden)', () => {
|
|
2650
2888
|
const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2651
2889
|
initialValues: { field_a: 'no', field_a_wrong: 'no' },
|
|
2652
2890
|
});
|
|
2653
|
-
expect(
|
|
2654
|
-
expect(
|
|
2891
|
+
expect(getField(fieldsHidden, 'field_b').isVisible).toBe(false);
|
|
2892
|
+
expect(getField(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
|
|
2655
2893
|
});
|
|
2656
2894
|
});
|
|
2657
2895
|
|
|
@@ -2661,7 +2899,7 @@ describe('createHeadlessForm', () => {
|
|
|
2661
2899
|
field_a: 'no',
|
|
2662
2900
|
},
|
|
2663
2901
|
});
|
|
2664
|
-
const dependentField =
|
|
2902
|
+
const dependentField = getField(fields, 'field_b');
|
|
2665
2903
|
expect(dependentField.isVisible).toBe(false);
|
|
2666
2904
|
expect(dependentField.value).toBe(undefined);
|
|
2667
2905
|
});
|
|
@@ -2672,7 +2910,7 @@ describe('createHeadlessForm', () => {
|
|
|
2672
2910
|
field_a: 'yes',
|
|
2673
2911
|
},
|
|
2674
2912
|
});
|
|
2675
|
-
const dependentField =
|
|
2913
|
+
const dependentField = getField(fields, 'field_b');
|
|
2676
2914
|
expect(dependentField.isVisible).toBe(true);
|
|
2677
2915
|
expect(dependentField.value).toBe(undefined);
|
|
2678
2916
|
});
|