@remoteoss/json-schema-form 0.3.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/CHANGELOG.md +12 -4
- package/dist/index.cjs +62 -20
- package/dist/index.js +62 -20
- package/dist/standalone.js +417 -803
- package/package.json +1 -1
- package/src/tests/createHeadlessForm.test.js +534 -266
- package/src/tests/helpers.js +233 -65
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
schemaInputTypeRadioDeprecated,
|
|
11
11
|
schemaInputTypeRadio,
|
|
12
12
|
schemaInputTypeRadioRequiredAndOptional,
|
|
13
|
+
schemaInputRadioOptionalNull,
|
|
14
|
+
schemaInputRadioOptionalConventional,
|
|
15
|
+
schemaInputTypeRadioOptionsWithDetails,
|
|
13
16
|
schemaInputTypeSelectSoloDeprecated,
|
|
14
17
|
schemaInputTypeSelectSolo,
|
|
15
18
|
schemaInputTypeSelectMultipleDeprecated,
|
|
@@ -44,6 +47,7 @@ import {
|
|
|
44
47
|
mockNestedFieldset,
|
|
45
48
|
mockGroupArrayInput,
|
|
46
49
|
schemaFieldsetScopedCondition,
|
|
50
|
+
schemaWithConditionalToFieldset,
|
|
47
51
|
schemaWithConditionalPresentationProperties,
|
|
48
52
|
schemaWithConditionalReadOnlyProperty,
|
|
49
53
|
schemaWithWrongConditional,
|
|
@@ -76,6 +80,17 @@ function friendlyError({ formErrors }) {
|
|
|
76
80
|
return formErrors;
|
|
77
81
|
}
|
|
78
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
|
+
|
|
79
94
|
beforeEach(() => {
|
|
80
95
|
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
81
96
|
});
|
|
@@ -419,6 +434,42 @@ describe('createHeadlessForm', () => {
|
|
|
419
434
|
});
|
|
420
435
|
|
|
421
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
|
+
|
|
422
473
|
it('support "text" field type', () => {
|
|
423
474
|
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
424
475
|
|
|
@@ -521,38 +572,43 @@ describe('createHeadlessForm', () => {
|
|
|
521
572
|
});
|
|
522
573
|
|
|
523
574
|
it('support "select" field type @deprecated', () => {
|
|
524
|
-
const
|
|
575
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
|
|
525
576
|
|
|
526
|
-
expect(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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'],
|
|
550
605
|
});
|
|
551
606
|
});
|
|
607
|
+
|
|
552
608
|
it('support "select" field type', () => {
|
|
553
|
-
const
|
|
609
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
554
610
|
|
|
555
|
-
const fieldSelect =
|
|
611
|
+
const fieldSelect = fields[0];
|
|
556
612
|
expect(fieldSelect).toMatchObject({
|
|
557
613
|
name: 'browsers',
|
|
558
614
|
label: 'Browsers (solo)',
|
|
@@ -575,6 +631,12 @@ describe('createHeadlessForm', () => {
|
|
|
575
631
|
});
|
|
576
632
|
|
|
577
633
|
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
634
|
+
|
|
635
|
+
assertOptionsAllowed({
|
|
636
|
+
handleValidation,
|
|
637
|
+
fieldName: 'browsers',
|
|
638
|
+
validOptions: ['chr', 'ff', 'ie'],
|
|
639
|
+
});
|
|
578
640
|
});
|
|
579
641
|
|
|
580
642
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
@@ -665,96 +727,178 @@ describe('createHeadlessForm', () => {
|
|
|
665
727
|
});
|
|
666
728
|
|
|
667
729
|
it('support "radio" field type @deprecated', () => {
|
|
668
|
-
const
|
|
730
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
669
731
|
|
|
670
|
-
expect(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
],
|
|
691
|
-
});
|
|
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
|
+
]);
|
|
692
752
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
753
|
+
assertOptionsAllowed({
|
|
754
|
+
handleValidation,
|
|
755
|
+
fieldName: 'has_siblings',
|
|
756
|
+
validOptions: ['yes', 'no'],
|
|
757
|
+
});
|
|
696
758
|
});
|
|
697
759
|
it('support "radio" field type', () => {
|
|
698
|
-
const
|
|
760
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
|
|
699
761
|
|
|
700
|
-
expect(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
],
|
|
721
|
-
});
|
|
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
|
+
]);
|
|
722
782
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
783
|
+
assertOptionsAllowed({
|
|
784
|
+
handleValidation,
|
|
785
|
+
fieldName: 'has_siblings',
|
|
786
|
+
validOptions: ['yes', 'no'],
|
|
787
|
+
});
|
|
726
788
|
});
|
|
727
789
|
|
|
728
790
|
it('support "radio" optional field', () => {
|
|
729
|
-
const
|
|
791
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
792
|
+
schemaInputTypeRadioRequiredAndOptional
|
|
793
|
+
);
|
|
794
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
730
795
|
|
|
731
|
-
expect(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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',
|
|
753
826
|
});
|
|
827
|
+
});
|
|
754
828
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it('support "radio" field type with extra info inside each option', () => {
|
|
876
|
+
const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
|
|
877
|
+
|
|
878
|
+
expect(result.fields).toHaveLength(1);
|
|
879
|
+
|
|
880
|
+
const fieldOptions = result.fields[0].options;
|
|
881
|
+
|
|
882
|
+
// The x-jsf-presentation content was spread to the root:
|
|
883
|
+
expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
|
|
884
|
+
expect(fieldOptions).toEqual([
|
|
885
|
+
{
|
|
886
|
+
label: 'Basic',
|
|
887
|
+
value: 'basic',
|
|
888
|
+
meta: {
|
|
889
|
+
displayCost: '$30.00/mo',
|
|
890
|
+
},
|
|
891
|
+
// Other x-* keywords are kept as it is.
|
|
892
|
+
'x-another': 'extra-thing',
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
label: 'Standard',
|
|
896
|
+
value: 'standard',
|
|
897
|
+
meta: {
|
|
898
|
+
displayCost: '$50.00/mo',
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
]);
|
|
758
902
|
});
|
|
759
903
|
|
|
760
904
|
it('support "number" field type', () => {
|
|
@@ -1152,46 +1296,6 @@ describe('createHeadlessForm', () => {
|
|
|
1152
1296
|
});
|
|
1153
1297
|
});
|
|
1154
1298
|
|
|
1155
|
-
it('supports "fieldset" field type', () => {
|
|
1156
|
-
const result = createHeadlessForm(
|
|
1157
|
-
JSONSchemaBuilder()
|
|
1158
|
-
.addInput({
|
|
1159
|
-
fieldset: mockFieldset,
|
|
1160
|
-
})
|
|
1161
|
-
.build()
|
|
1162
|
-
);
|
|
1163
|
-
|
|
1164
|
-
expect(result).toMatchObject({
|
|
1165
|
-
fields: [
|
|
1166
|
-
{
|
|
1167
|
-
description: 'Fieldset description',
|
|
1168
|
-
label: 'Fieldset title',
|
|
1169
|
-
name: 'fieldset',
|
|
1170
|
-
type: 'fieldset',
|
|
1171
|
-
required: false,
|
|
1172
|
-
fields: [
|
|
1173
|
-
{
|
|
1174
|
-
description: 'The number of your national identification (max 10 digits)',
|
|
1175
|
-
label: 'ID number',
|
|
1176
|
-
name: 'id_number',
|
|
1177
|
-
type: 'text',
|
|
1178
|
-
required: true,
|
|
1179
|
-
},
|
|
1180
|
-
{
|
|
1181
|
-
description: 'How many open tabs do you have?',
|
|
1182
|
-
label: 'Tabs',
|
|
1183
|
-
maximum: 10,
|
|
1184
|
-
minimum: 1,
|
|
1185
|
-
name: 'tabs',
|
|
1186
|
-
type: 'number',
|
|
1187
|
-
required: false,
|
|
1188
|
-
},
|
|
1189
|
-
],
|
|
1190
|
-
},
|
|
1191
|
-
],
|
|
1192
|
-
});
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
1299
|
it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
|
|
1196
1300
|
const result = createHeadlessForm(
|
|
1197
1301
|
JSONSchemaBuilder()
|
|
@@ -1257,130 +1361,298 @@ describe('createHeadlessForm', () => {
|
|
|
1257
1361
|
});
|
|
1258
1362
|
});
|
|
1259
1363
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
})
|
|
1266
|
-
.build()
|
|
1267
|
-
);
|
|
1268
|
-
|
|
1269
|
-
expect(result).toMatchObject({
|
|
1270
|
-
fields: [
|
|
1271
|
-
{
|
|
1272
|
-
label: 'Nested fieldset title',
|
|
1273
|
-
description: 'Nested fieldset description',
|
|
1274
|
-
name: 'nestedFieldset',
|
|
1275
|
-
type: 'fieldset',
|
|
1276
|
-
required: false,
|
|
1277
|
-
fields: [
|
|
1278
|
-
{
|
|
1279
|
-
description: 'Fieldset description',
|
|
1280
|
-
label: 'Fieldset title',
|
|
1281
|
-
name: 'innerFieldset',
|
|
1282
|
-
type: 'fieldset',
|
|
1283
|
-
required: false,
|
|
1284
|
-
fields: [
|
|
1285
|
-
{
|
|
1286
|
-
description: 'The number of your national identification (max 10 digits)',
|
|
1287
|
-
label: 'ID number',
|
|
1288
|
-
name: 'id_number',
|
|
1289
|
-
type: 'text',
|
|
1290
|
-
required: true,
|
|
1291
|
-
},
|
|
1292
|
-
{
|
|
1293
|
-
description: 'How many open tabs do you have?',
|
|
1294
|
-
label: 'Tabs',
|
|
1295
|
-
maximum: 10,
|
|
1296
|
-
minimum: 1,
|
|
1297
|
-
name: 'tabs',
|
|
1298
|
-
type: 'number',
|
|
1299
|
-
required: false,
|
|
1300
|
-
},
|
|
1301
|
-
],
|
|
1302
|
-
},
|
|
1303
|
-
],
|
|
1364
|
+
describe('supports "fieldset" field type', () => {
|
|
1365
|
+
it('supports basic case', () => {
|
|
1366
|
+
const result = createHeadlessForm({
|
|
1367
|
+
properties: {
|
|
1368
|
+
fieldset: mockFieldset,
|
|
1304
1369
|
},
|
|
1305
|
-
|
|
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
|
+
});
|
|
1306
1401
|
});
|
|
1307
|
-
});
|
|
1308
1402
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1403
|
+
it('supports nested fieldset (fieldset inside fieldset)', () => {
|
|
1404
|
+
const result = createHeadlessForm(
|
|
1405
|
+
JSONSchemaBuilder()
|
|
1406
|
+
.addInput({
|
|
1407
|
+
nestedFieldset: mockNestedFieldset,
|
|
1408
|
+
})
|
|
1409
|
+
.build()
|
|
1410
|
+
);
|
|
1312
1411
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
+
});
|
|
1318
1450
|
});
|
|
1319
1451
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
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({
|
|
1323
1458
|
child: {
|
|
1324
|
-
has_child: '
|
|
1459
|
+
has_child: 'Required field',
|
|
1325
1460
|
},
|
|
1326
|
-
})
|
|
1327
|
-
).toBeUndefined();
|
|
1461
|
+
});
|
|
1328
1462
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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({
|
|
1332
1480
|
child: {
|
|
1333
|
-
|
|
1481
|
+
age: 'Required field',
|
|
1334
1482
|
},
|
|
1335
|
-
})
|
|
1336
|
-
|
|
1337
|
-
child
|
|
1338
|
-
|
|
1339
|
-
|
|
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();
|
|
1340
1505
|
});
|
|
1341
1506
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
validateForm(
|
|
1345
|
-
child: {
|
|
1346
|
-
has_child: 'yes',
|
|
1347
|
-
age: 15,
|
|
1348
|
-
},
|
|
1349
|
-
})
|
|
1350
|
-
).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));
|
|
1351
1510
|
|
|
1352
|
-
|
|
1353
|
-
expect(
|
|
1354
|
-
validateForm({
|
|
1511
|
+
const formValues = {
|
|
1355
1512
|
child: {
|
|
1356
1513
|
has_child: 'yes',
|
|
1357
1514
|
age: 15,
|
|
1358
|
-
passport_id: 'asdf',
|
|
1359
1515
|
},
|
|
1360
|
-
}
|
|
1361
|
-
).toBeUndefined();
|
|
1362
|
-
});
|
|
1516
|
+
};
|
|
1363
1517
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1518
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1519
|
+
expect(formValues.child.age).toBe(15);
|
|
1367
1520
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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)
|
|
1374
1537
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1538
|
+
// Setup (arrange)
|
|
1539
|
+
const { fields, handleValidation } = createHeadlessForm(schemaWithConditionalToFieldset);
|
|
1377
1540
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
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
|
+
});
|
|
1381
1583
|
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
+
});
|
|
1635
|
+
|
|
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();
|
|
1641
|
+
|
|
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
|
+
);
|
|
1646
|
+
|
|
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
|
+
});
|
|
1384
1656
|
});
|
|
1385
1657
|
|
|
1386
1658
|
it('support "email" field type', () => {
|
|
@@ -1699,14 +1971,12 @@ describe('createHeadlessForm', () => {
|
|
|
1699
1971
|
strictInputType: false,
|
|
1700
1972
|
});
|
|
1701
1973
|
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
const aFieldInRoot = getByName(fields, 'a_string');
|
|
1974
|
+
const aFieldInRoot = getField(fields, 'a_string');
|
|
1705
1975
|
// It's the entire json schema
|
|
1706
1976
|
expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
|
|
1707
1977
|
|
|
1708
|
-
const aFieldset =
|
|
1709
|
-
const aFieldInTheFieldset =
|
|
1978
|
+
const aFieldset = getField(fields, 'a_object');
|
|
1979
|
+
const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
|
|
1710
1980
|
|
|
1711
1981
|
// It's only the json schema of that fieldset
|
|
1712
1982
|
expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
|
|
@@ -2166,8 +2436,6 @@ describe('createHeadlessForm', () => {
|
|
|
2166
2436
|
});
|
|
2167
2437
|
|
|
2168
2438
|
describe('when a JSON Schema is provided', () => {
|
|
2169
|
-
const getByName = (fields, name) => fields.find((f) => f.name === name);
|
|
2170
|
-
|
|
2171
2439
|
describe('and all fields are optional', () => {
|
|
2172
2440
|
let handleValidation;
|
|
2173
2441
|
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
@@ -2574,23 +2842,23 @@ describe('createHeadlessForm', () => {
|
|
|
2574
2842
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2575
2843
|
field_a: null,
|
|
2576
2844
|
});
|
|
2577
|
-
expect(
|
|
2845
|
+
expect(getField(fields, 'field_b').isVisible).toBe(false);
|
|
2578
2846
|
});
|
|
2579
2847
|
|
|
2580
2848
|
it('given a match, runs "then" (turns visible and editable)', () => {
|
|
2581
2849
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2582
2850
|
initialValues: { field_a: 'yes' },
|
|
2583
2851
|
});
|
|
2584
|
-
expect(
|
|
2585
|
-
expect(
|
|
2852
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2853
|
+
expect(getField(fields, 'field_b').readOnly).toBe(false);
|
|
2586
2854
|
});
|
|
2587
2855
|
|
|
2588
2856
|
it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
|
|
2589
2857
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2590
2858
|
initialValues: { field_a: 'no' },
|
|
2591
2859
|
});
|
|
2592
|
-
expect(
|
|
2593
|
-
expect(
|
|
2860
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2861
|
+
expect(getField(fields, 'field_b').readOnly).toBe(true);
|
|
2594
2862
|
});
|
|
2595
2863
|
});
|
|
2596
2864
|
|
|
@@ -2602,26 +2870,26 @@ describe('createHeadlessForm', () => {
|
|
|
2602
2870
|
initialValues: { field_a: null, field_a_wrong: null },
|
|
2603
2871
|
});
|
|
2604
2872
|
// The dependent correct field gets hidden, but...
|
|
2605
|
-
expect(
|
|
2873
|
+
expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
|
|
2606
2874
|
// ...the dependent wrong field stays visible because the
|
|
2607
2875
|
// conditional is wrong (it's missing the if.required[])
|
|
2608
|
-
expect(
|
|
2876
|
+
expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
|
|
2609
2877
|
});
|
|
2610
2878
|
|
|
2611
2879
|
it('given a match ("yes"), both runs "then" (turn visible)', () => {
|
|
2612
2880
|
const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2613
2881
|
initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
|
|
2614
2882
|
});
|
|
2615
|
-
expect(
|
|
2616
|
-
expect(
|
|
2883
|
+
expect(getField(fieldsVisible, 'field_b').isVisible).toBe(true);
|
|
2884
|
+
expect(getField(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
|
|
2617
2885
|
});
|
|
2618
2886
|
|
|
2619
2887
|
it('not given a match ("no"), both run else (stay hidden)', () => {
|
|
2620
2888
|
const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2621
2889
|
initialValues: { field_a: 'no', field_a_wrong: 'no' },
|
|
2622
2890
|
});
|
|
2623
|
-
expect(
|
|
2624
|
-
expect(
|
|
2891
|
+
expect(getField(fieldsHidden, 'field_b').isVisible).toBe(false);
|
|
2892
|
+
expect(getField(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
|
|
2625
2893
|
});
|
|
2626
2894
|
});
|
|
2627
2895
|
|
|
@@ -2631,7 +2899,7 @@ describe('createHeadlessForm', () => {
|
|
|
2631
2899
|
field_a: 'no',
|
|
2632
2900
|
},
|
|
2633
2901
|
});
|
|
2634
|
-
const dependentField =
|
|
2902
|
+
const dependentField = getField(fields, 'field_b');
|
|
2635
2903
|
expect(dependentField.isVisible).toBe(false);
|
|
2636
2904
|
expect(dependentField.value).toBe(undefined);
|
|
2637
2905
|
});
|
|
@@ -2642,7 +2910,7 @@ describe('createHeadlessForm', () => {
|
|
|
2642
2910
|
field_a: 'yes',
|
|
2643
2911
|
},
|
|
2644
2912
|
});
|
|
2645
|
-
const dependentField =
|
|
2913
|
+
const dependentField = getField(fields, 'field_b');
|
|
2646
2914
|
expect(dependentField.isVisible).toBe(true);
|
|
2647
2915
|
expect(dependentField.value).toBe(undefined);
|
|
2648
2916
|
});
|