@remoteoss/json-schema-form 0.3.0-beta.0 → 0.4.1-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.
- package/CHANGELOG.md +19 -4
- package/dist/index.cjs +74 -22
- package/dist/index.js +74 -22
- package/dist/standalone.js +429 -805
- package/package.json +1 -1
- package/src/tests/createHeadlessForm.test.js +537 -266
- package/src/tests/helpers.custom.js +1 -1
- 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,37 @@ 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(validateForm({ [fieldName]: value })).toBeUndefined();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Any other arbitrary value is not valid.
|
|
446
|
+
expect(validateForm({ [fieldName]: 'blah-blah' })).toEqual({
|
|
447
|
+
[fieldName]: 'The option "blah-blah" is not valid.',
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Given undefined, it says it's a required field.
|
|
451
|
+
expect(validateForm({})).toEqual({
|
|
452
|
+
[fieldName]: 'Required field',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// As required field, empty string ("") is also considered empty. @BUG RMT-518
|
|
456
|
+
// Expectation: The error to be "The option '' is not valid."
|
|
457
|
+
expect(validateForm({ [fieldName]: '' })).toEqual({
|
|
458
|
+
[fieldName]: 'Required field',
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// As required field, null is also considered empty @BUG RMT-518
|
|
462
|
+
// Expectation: The error to be "The option null is not valid."
|
|
463
|
+
expect(validateForm({ [fieldName]: null })).toEqual({
|
|
464
|
+
[fieldName]: 'Required field',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
422
468
|
it('support "text" field type', () => {
|
|
423
469
|
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
424
470
|
|
|
@@ -521,38 +567,43 @@ describe('createHeadlessForm', () => {
|
|
|
521
567
|
});
|
|
522
568
|
|
|
523
569
|
it('support "select" field type @deprecated', () => {
|
|
524
|
-
const
|
|
570
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
|
|
525
571
|
|
|
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
|
-
|
|
572
|
+
expect(fields).toMatchObject([
|
|
573
|
+
{
|
|
574
|
+
description: 'Life Insurance',
|
|
575
|
+
label: 'Benefits (solo)',
|
|
576
|
+
name: 'benefits',
|
|
577
|
+
placeholder: 'Select...',
|
|
578
|
+
type: 'select',
|
|
579
|
+
options: [
|
|
580
|
+
{
|
|
581
|
+
label: 'Medical Insurance',
|
|
582
|
+
value: 'Medical Insurance',
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
label: 'Health Insurance',
|
|
586
|
+
value: 'Health Insurance',
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
label: 'Travel Bonus',
|
|
590
|
+
value: 'Travel Bonus',
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
},
|
|
594
|
+
]);
|
|
595
|
+
|
|
596
|
+
assertOptionsAllowed({
|
|
597
|
+
handleValidation,
|
|
598
|
+
fieldName: 'benefits',
|
|
599
|
+
validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
|
|
550
600
|
});
|
|
551
601
|
});
|
|
602
|
+
|
|
552
603
|
it('support "select" field type', () => {
|
|
553
|
-
const
|
|
604
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
554
605
|
|
|
555
|
-
const fieldSelect =
|
|
606
|
+
const fieldSelect = fields[0];
|
|
556
607
|
expect(fieldSelect).toMatchObject({
|
|
557
608
|
name: 'browsers',
|
|
558
609
|
label: 'Browsers (solo)',
|
|
@@ -575,6 +626,12 @@ describe('createHeadlessForm', () => {
|
|
|
575
626
|
});
|
|
576
627
|
|
|
577
628
|
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
629
|
+
|
|
630
|
+
assertOptionsAllowed({
|
|
631
|
+
handleValidation,
|
|
632
|
+
fieldName: 'browsers',
|
|
633
|
+
validOptions: ['chr', 'ff', 'ie'],
|
|
634
|
+
});
|
|
578
635
|
});
|
|
579
636
|
|
|
580
637
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
@@ -665,96 +722,186 @@ describe('createHeadlessForm', () => {
|
|
|
665
722
|
});
|
|
666
723
|
|
|
667
724
|
it('support "radio" field type @deprecated', () => {
|
|
668
|
-
const
|
|
725
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
669
726
|
|
|
670
|
-
expect(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
],
|
|
691
|
-
});
|
|
727
|
+
expect(fields).toMatchObject([
|
|
728
|
+
{
|
|
729
|
+
description: 'Do you have any siblings?',
|
|
730
|
+
label: 'Has siblings',
|
|
731
|
+
name: 'has_siblings',
|
|
732
|
+
options: [
|
|
733
|
+
{
|
|
734
|
+
label: 'Yes',
|
|
735
|
+
value: 'yes',
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
label: 'No',
|
|
739
|
+
value: 'no',
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
required: true,
|
|
743
|
+
schema: expect.any(Object),
|
|
744
|
+
type: 'radio',
|
|
745
|
+
},
|
|
746
|
+
]);
|
|
692
747
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
748
|
+
assertOptionsAllowed({
|
|
749
|
+
handleValidation,
|
|
750
|
+
fieldName: 'has_siblings',
|
|
751
|
+
validOptions: ['yes', 'no'],
|
|
752
|
+
});
|
|
696
753
|
});
|
|
697
754
|
it('support "radio" field type', () => {
|
|
698
|
-
const
|
|
755
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
|
|
699
756
|
|
|
700
|
-
expect(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
],
|
|
721
|
-
});
|
|
757
|
+
expect(fields).toMatchObject([
|
|
758
|
+
{
|
|
759
|
+
description: 'Do you have any siblings?',
|
|
760
|
+
label: 'Has siblings',
|
|
761
|
+
name: 'has_siblings',
|
|
762
|
+
options: [
|
|
763
|
+
{
|
|
764
|
+
label: 'Yes',
|
|
765
|
+
value: 'yes',
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
label: 'No',
|
|
769
|
+
value: 'no',
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
required: true,
|
|
773
|
+
schema: expect.any(Object),
|
|
774
|
+
type: 'radio',
|
|
775
|
+
},
|
|
776
|
+
]);
|
|
722
777
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
778
|
+
assertOptionsAllowed({
|
|
779
|
+
handleValidation,
|
|
780
|
+
fieldName: 'has_siblings',
|
|
781
|
+
validOptions: ['yes', 'no'],
|
|
782
|
+
});
|
|
726
783
|
});
|
|
727
784
|
|
|
728
785
|
it('support "radio" optional field', () => {
|
|
729
|
-
const
|
|
786
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
787
|
+
schemaInputTypeRadioRequiredAndOptional
|
|
788
|
+
);
|
|
789
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
730
790
|
|
|
731
|
-
expect(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
791
|
+
expect(fields).toMatchObject([
|
|
792
|
+
{},
|
|
793
|
+
{
|
|
794
|
+
name: 'has_car',
|
|
795
|
+
label: 'Has car',
|
|
796
|
+
description: 'Do you have a car? (optional field, check oneOf)',
|
|
797
|
+
options: [
|
|
798
|
+
{
|
|
799
|
+
label: 'Yes',
|
|
800
|
+
value: 'yes',
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
label: 'No',
|
|
804
|
+
value: 'no',
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
required: false,
|
|
808
|
+
schema: expect.any(Object),
|
|
809
|
+
type: 'radio',
|
|
810
|
+
},
|
|
811
|
+
]);
|
|
812
|
+
|
|
813
|
+
expect(
|
|
814
|
+
validateForm({
|
|
815
|
+
has_siblings: 'yes',
|
|
816
|
+
has_car: 'yes',
|
|
817
|
+
})
|
|
818
|
+
).toBeUndefined();
|
|
819
|
+
expect(validateForm({})).toEqual({
|
|
820
|
+
has_siblings: 'Required field',
|
|
753
821
|
});
|
|
822
|
+
});
|
|
754
823
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
824
|
+
describe('support "radio" optional field - more examples @BUG RMT-518', () => {
|
|
825
|
+
function assertCommonBehavior(validateForm) {
|
|
826
|
+
// Note: Very similar to assertOptionsAllowed()
|
|
827
|
+
// We could reuse it in a next iteration.
|
|
828
|
+
|
|
829
|
+
// Happy path
|
|
830
|
+
expect(validateForm({ has_car: 'yes' })).toBeUndefined();
|
|
831
|
+
|
|
832
|
+
// Accepts undefined field
|
|
833
|
+
expect(validateForm({})).toBeUndefined();
|
|
834
|
+
|
|
835
|
+
// Does not accept other values
|
|
836
|
+
expect(validateForm({ has_car: 'blah-blah' })).toEqual({
|
|
837
|
+
has_car: 'The option "blah-blah" is not valid.',
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// Does not accept "null" as string
|
|
841
|
+
expect(validateForm({ has_car: 'null' })).toEqual({
|
|
842
|
+
has_car: 'The option "null" is not valid.',
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Accepts empty string ("") — @BUG RMT-518
|
|
846
|
+
// Expectation: Does not accept empty string ("")
|
|
847
|
+
expect(validateForm({ has_car: '' })).toBeUndefined();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
it('normal optional (conventional way)', () => {
|
|
851
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
|
|
852
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
853
|
+
|
|
854
|
+
assertCommonBehavior(validateForm);
|
|
855
|
+
|
|
856
|
+
// Accepts null, even though it shoudln't @BUG RMT-518
|
|
857
|
+
// This is for cases where we (Remote) still have incorrect
|
|
858
|
+
// JSON Schemas in our Platform.
|
|
859
|
+
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
860
|
+
// Expected:
|
|
861
|
+
// // Does NOT accept null value
|
|
862
|
+
// expect(validateForm({ has_car: null })).toEqual({
|
|
863
|
+
// has_car: 'The option null is not valid.',
|
|
864
|
+
// });
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('with null option (as Remote does)', () => {
|
|
868
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
|
|
869
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
870
|
+
|
|
871
|
+
assertCommonBehavior(validateForm);
|
|
872
|
+
|
|
873
|
+
// Accepts null value
|
|
874
|
+
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('support "radio" field type with extra info inside each option', () => {
|
|
879
|
+
const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
|
|
880
|
+
|
|
881
|
+
expect(result.fields).toHaveLength(1);
|
|
882
|
+
|
|
883
|
+
const fieldOptions = result.fields[0].options;
|
|
884
|
+
|
|
885
|
+
// The x-jsf-presentation content was spread to the root:
|
|
886
|
+
expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
|
|
887
|
+
expect(fieldOptions).toEqual([
|
|
888
|
+
{
|
|
889
|
+
label: 'Basic',
|
|
890
|
+
value: 'basic',
|
|
891
|
+
meta: {
|
|
892
|
+
displayCost: '$30.00/mo',
|
|
893
|
+
},
|
|
894
|
+
// Other x-* keywords are kept as it is.
|
|
895
|
+
'x-another': 'extra-thing',
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
label: 'Standard',
|
|
899
|
+
value: 'standard',
|
|
900
|
+
meta: {
|
|
901
|
+
displayCost: '$50.00/mo',
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
]);
|
|
758
905
|
});
|
|
759
906
|
|
|
760
907
|
it('support "number" field type', () => {
|
|
@@ -1152,46 +1299,6 @@ describe('createHeadlessForm', () => {
|
|
|
1152
1299
|
});
|
|
1153
1300
|
});
|
|
1154
1301
|
|
|
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
1302
|
it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
|
|
1196
1303
|
const result = createHeadlessForm(
|
|
1197
1304
|
JSONSchemaBuilder()
|
|
@@ -1257,130 +1364,298 @@ describe('createHeadlessForm', () => {
|
|
|
1257
1364
|
});
|
|
1258
1365
|
});
|
|
1259
1366
|
|
|
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
|
-
],
|
|
1367
|
+
describe('supports "fieldset" field type', () => {
|
|
1368
|
+
it('supports basic case', () => {
|
|
1369
|
+
const result = createHeadlessForm({
|
|
1370
|
+
properties: {
|
|
1371
|
+
fieldset: mockFieldset,
|
|
1304
1372
|
},
|
|
1305
|
-
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
expect(result).toMatchObject({
|
|
1376
|
+
fields: [
|
|
1377
|
+
{
|
|
1378
|
+
description: 'Fieldset description',
|
|
1379
|
+
label: 'Fieldset title',
|
|
1380
|
+
name: 'fieldset',
|
|
1381
|
+
type: 'fieldset',
|
|
1382
|
+
required: false,
|
|
1383
|
+
fields: [
|
|
1384
|
+
{
|
|
1385
|
+
description: 'The number of your national identification (max 10 digits)',
|
|
1386
|
+
label: 'ID number',
|
|
1387
|
+
name: 'id_number',
|
|
1388
|
+
type: 'text',
|
|
1389
|
+
required: true,
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
description: 'How many open tabs do you have?',
|
|
1393
|
+
label: 'Tabs',
|
|
1394
|
+
maximum: 10,
|
|
1395
|
+
minimum: 1,
|
|
1396
|
+
name: 'tabs',
|
|
1397
|
+
type: 'number',
|
|
1398
|
+
required: false,
|
|
1399
|
+
},
|
|
1400
|
+
],
|
|
1401
|
+
},
|
|
1402
|
+
],
|
|
1403
|
+
});
|
|
1306
1404
|
});
|
|
1307
|
-
});
|
|
1308
1405
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1406
|
+
it('supports nested fieldset (fieldset inside fieldset)', () => {
|
|
1407
|
+
const result = createHeadlessForm(
|
|
1408
|
+
JSONSchemaBuilder()
|
|
1409
|
+
.addInput({
|
|
1410
|
+
nestedFieldset: mockNestedFieldset,
|
|
1411
|
+
})
|
|
1412
|
+
.build()
|
|
1413
|
+
);
|
|
1312
1414
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1415
|
+
expect(result).toMatchObject({
|
|
1416
|
+
fields: [
|
|
1417
|
+
{
|
|
1418
|
+
label: 'Nested fieldset title',
|
|
1419
|
+
description: 'Nested fieldset description',
|
|
1420
|
+
name: 'nestedFieldset',
|
|
1421
|
+
type: 'fieldset',
|
|
1422
|
+
required: false,
|
|
1423
|
+
fields: [
|
|
1424
|
+
{
|
|
1425
|
+
description: 'Fieldset description',
|
|
1426
|
+
label: 'Fieldset title',
|
|
1427
|
+
name: 'innerFieldset',
|
|
1428
|
+
type: 'fieldset',
|
|
1429
|
+
required: false,
|
|
1430
|
+
fields: [
|
|
1431
|
+
{
|
|
1432
|
+
description: 'The number of your national identification (max 10 digits)',
|
|
1433
|
+
label: 'ID number',
|
|
1434
|
+
name: 'id_number',
|
|
1435
|
+
type: 'text',
|
|
1436
|
+
required: true,
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
description: 'How many open tabs do you have?',
|
|
1440
|
+
label: 'Tabs',
|
|
1441
|
+
maximum: 10,
|
|
1442
|
+
minimum: 1,
|
|
1443
|
+
name: 'tabs',
|
|
1444
|
+
type: 'number',
|
|
1445
|
+
required: false,
|
|
1446
|
+
},
|
|
1447
|
+
],
|
|
1448
|
+
},
|
|
1449
|
+
],
|
|
1450
|
+
},
|
|
1451
|
+
],
|
|
1452
|
+
});
|
|
1318
1453
|
});
|
|
1319
1454
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
validateForm(
|
|
1455
|
+
it('supported "fieldset" with scoped conditionals', () => {
|
|
1456
|
+
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
1457
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1458
|
+
|
|
1459
|
+
// The "child.has_child" is required
|
|
1460
|
+
expect(validateForm({})).toEqual({
|
|
1323
1461
|
child: {
|
|
1324
|
-
has_child: '
|
|
1462
|
+
has_child: 'Required field',
|
|
1325
1463
|
},
|
|
1326
|
-
})
|
|
1327
|
-
).toBeUndefined();
|
|
1464
|
+
});
|
|
1328
1465
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1466
|
+
// The "child.no" is valid
|
|
1467
|
+
expect(
|
|
1468
|
+
validateForm({
|
|
1469
|
+
child: {
|
|
1470
|
+
has_child: 'no',
|
|
1471
|
+
},
|
|
1472
|
+
})
|
|
1473
|
+
).toBeUndefined();
|
|
1474
|
+
|
|
1475
|
+
// Invalid because it expect child.age too
|
|
1476
|
+
expect(
|
|
1477
|
+
validateForm({
|
|
1478
|
+
child: {
|
|
1479
|
+
has_child: 'yes',
|
|
1480
|
+
},
|
|
1481
|
+
})
|
|
1482
|
+
).toEqual({
|
|
1332
1483
|
child: {
|
|
1333
|
-
|
|
1484
|
+
age: 'Required field',
|
|
1334
1485
|
},
|
|
1335
|
-
})
|
|
1336
|
-
|
|
1337
|
-
child
|
|
1338
|
-
|
|
1339
|
-
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
// Valid without optional child.passport_id
|
|
1489
|
+
expect(
|
|
1490
|
+
validateForm({
|
|
1491
|
+
child: {
|
|
1492
|
+
has_child: 'yes',
|
|
1493
|
+
age: 15,
|
|
1494
|
+
},
|
|
1495
|
+
})
|
|
1496
|
+
).toBeUndefined();
|
|
1497
|
+
|
|
1498
|
+
// Valid with optional child.passport_id
|
|
1499
|
+
expect(
|
|
1500
|
+
validateForm({
|
|
1501
|
+
child: {
|
|
1502
|
+
has_child: 'yes',
|
|
1503
|
+
age: 15,
|
|
1504
|
+
passport_id: 'asdf',
|
|
1505
|
+
},
|
|
1506
|
+
})
|
|
1507
|
+
).toBeUndefined();
|
|
1340
1508
|
});
|
|
1341
1509
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
validateForm(
|
|
1345
|
-
child: {
|
|
1346
|
-
has_child: 'yes',
|
|
1347
|
-
age: 15,
|
|
1348
|
-
},
|
|
1349
|
-
})
|
|
1350
|
-
).toBeUndefined();
|
|
1510
|
+
it('should set any nested "fieldset" form values to null when they are invisible', async () => {
|
|
1511
|
+
const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
|
|
1512
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1351
1513
|
|
|
1352
|
-
|
|
1353
|
-
expect(
|
|
1354
|
-
validateForm({
|
|
1514
|
+
const formValues = {
|
|
1355
1515
|
child: {
|
|
1356
1516
|
has_child: 'yes',
|
|
1357
1517
|
age: 15,
|
|
1358
|
-
passport_id: 'asdf',
|
|
1359
1518
|
},
|
|
1360
|
-
}
|
|
1361
|
-
).toBeUndefined();
|
|
1362
|
-
});
|
|
1519
|
+
};
|
|
1363
1520
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1521
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1522
|
+
expect(formValues.child.age).toBe(15);
|
|
1367
1523
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1524
|
+
formValues.child.has_child = 'no';
|
|
1525
|
+
// form value updates re-validate; see computeYupSchema()
|
|
1526
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1527
|
+
|
|
1528
|
+
// when child.has_child is 'no' child.age is invisible
|
|
1529
|
+
expect(formValues.child.age).toBe(null);
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
describe('supports conditionals to fieldsets', () => {
|
|
1533
|
+
// To not mix the concepts:
|
|
1534
|
+
// - Scoped conditionals: Conditionals written inside a fieldset
|
|
1535
|
+
// - Conditionals to fieldsets: Root conditionals that affect a fieldset
|
|
1536
|
+
|
|
1537
|
+
// This describe has sequential tests, covering the following:
|
|
1538
|
+
// If the working_hours > 30,
|
|
1539
|
+
// Then the fieldset perks.food changes (the "no" option gets removed)
|
|
1540
|
+
|
|
1541
|
+
// Setup (arrange)
|
|
1542
|
+
const { fields, handleValidation } = createHeadlessForm(schemaWithConditionalToFieldset);
|
|
1543
|
+
|
|
1544
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1545
|
+
const originalFood = getField(fields, 'perks', 'food');
|
|
1546
|
+
|
|
1547
|
+
const perksForLowWorkHours = {
|
|
1548
|
+
food: 'no', // this option will be removed when the condition happens.
|
|
1549
|
+
retirement: 'basic',
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
it('by default, the Perks.food has 4 options', () => {
|
|
1553
|
+
expect(originalFood.options).toHaveLength(4);
|
|
1554
|
+
expect(originalFood.description).toBeUndefined();
|
|
1555
|
+
|
|
1556
|
+
// Ensure the perks are required
|
|
1557
|
+
expect(validateForm({})).toEqual({
|
|
1558
|
+
work_hours_per_week: 'Required field',
|
|
1559
|
+
perks: {
|
|
1560
|
+
food: 'Required field',
|
|
1561
|
+
retirement: 'Required field',
|
|
1562
|
+
},
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// Given low work hours, the form is valid.
|
|
1566
|
+
expect(
|
|
1567
|
+
validateForm({
|
|
1568
|
+
work_hours_per_week: 5,
|
|
1569
|
+
perks: perksForLowWorkHours,
|
|
1570
|
+
})
|
|
1571
|
+
).toBeUndefined();
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
it('Given a lot work hours, the perks.food options change', () => {
|
|
1575
|
+
expect(
|
|
1576
|
+
validateForm({
|
|
1577
|
+
work_hours_per_week: 35,
|
|
1578
|
+
})
|
|
1579
|
+
).toEqual({
|
|
1580
|
+
pto: 'Required field', // Sanity-check - this field gets required too.
|
|
1581
|
+
perks: {
|
|
1582
|
+
food: 'Required field',
|
|
1583
|
+
retirement: 'Required field',
|
|
1584
|
+
},
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
// The fieldset changed!
|
|
1588
|
+
const foodField = getField(fields, 'perks', 'food');
|
|
1589
|
+
// perks.food options changed ("No" was removed)
|
|
1590
|
+
expect(foodField.options).toHaveLength(3);
|
|
1591
|
+
|
|
1592
|
+
// Ensure the "no" option is no longer accepted:
|
|
1593
|
+
// This is a very important test in case the UI fails for some reason.
|
|
1594
|
+
expect(
|
|
1595
|
+
validateForm({
|
|
1596
|
+
work_hours_per_week: 35,
|
|
1597
|
+
pto: 20,
|
|
1598
|
+
perks: perksForLowWorkHours,
|
|
1599
|
+
})
|
|
1600
|
+
).toEqual({
|
|
1601
|
+
perks: {
|
|
1602
|
+
food: 'The option "no" is not valid.',
|
|
1603
|
+
},
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
// perks.food has a new description
|
|
1607
|
+
expect(foodField.description).toBe("Above 30 hours, the 'no' option disappears.");
|
|
1608
|
+
// pto has a new description
|
|
1609
|
+
expect(getField(fields, 'pto').description).toBe(
|
|
1610
|
+
'Above 30 hours, the PTO needs to be at least 20 days.'
|
|
1611
|
+
);
|
|
1612
|
+
|
|
1613
|
+
// Sanity-check: Now the PTO also has a minimum value
|
|
1614
|
+
expect(
|
|
1615
|
+
validateForm({
|
|
1616
|
+
work_hours_per_week: 35,
|
|
1617
|
+
pto: 5, // too low
|
|
1618
|
+
perks: { food: 'lunch', retirement: 'basic' },
|
|
1619
|
+
})
|
|
1620
|
+
).toEqual({
|
|
1621
|
+
pto: 'Must be greater or equal to 20',
|
|
1622
|
+
});
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
it('When changing back to low work hours, the perks.food goes back to the original state', () => {
|
|
1626
|
+
expect(
|
|
1627
|
+
validateForm({
|
|
1628
|
+
work_hours_per_week: 10,
|
|
1629
|
+
pto: 5,
|
|
1630
|
+
})
|
|
1631
|
+
).toEqual({
|
|
1632
|
+
perks: {
|
|
1633
|
+
food: 'Required field',
|
|
1634
|
+
retirement: 'Required field',
|
|
1635
|
+
},
|
|
1636
|
+
// ...pto is minimum error is gone! (sanity-check)
|
|
1637
|
+
});
|
|
1374
1638
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1639
|
+
const foodField = getField(fields, 'perks', 'food');
|
|
1640
|
+
// ...Number of perks.food options was back to the original (4)
|
|
1641
|
+
expect(foodField.options).toHaveLength(4);
|
|
1642
|
+
// ...Food description was back to the original
|
|
1643
|
+
expect(foodField.description).toBeUndefined();
|
|
1377
1644
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1645
|
+
// @BUG RMT-58 PTO description should disappear, but it didn't.
|
|
1646
|
+
expect(getField(fields, 'pto').description).toBe(
|
|
1647
|
+
'Above 30 hours, the PTO needs to be at least 20 days.'
|
|
1648
|
+
);
|
|
1381
1649
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1650
|
+
// Given again "low perks", the form valid.
|
|
1651
|
+
expect(
|
|
1652
|
+
validateForm({
|
|
1653
|
+
work_hours_per_week: 10,
|
|
1654
|
+
perks: perksForLowWorkHours,
|
|
1655
|
+
})
|
|
1656
|
+
).toBeUndefined();
|
|
1657
|
+
});
|
|
1658
|
+
});
|
|
1384
1659
|
});
|
|
1385
1660
|
|
|
1386
1661
|
it('support "email" field type', () => {
|
|
@@ -1699,14 +1974,12 @@ describe('createHeadlessForm', () => {
|
|
|
1699
1974
|
strictInputType: false,
|
|
1700
1975
|
});
|
|
1701
1976
|
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
const aFieldInRoot = getByName(fields, 'a_string');
|
|
1977
|
+
const aFieldInRoot = getField(fields, 'a_string');
|
|
1705
1978
|
// It's the entire json schema
|
|
1706
1979
|
expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
|
|
1707
1980
|
|
|
1708
|
-
const aFieldset =
|
|
1709
|
-
const aFieldInTheFieldset =
|
|
1981
|
+
const aFieldset = getField(fields, 'a_object');
|
|
1982
|
+
const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
|
|
1710
1983
|
|
|
1711
1984
|
// It's only the json schema of that fieldset
|
|
1712
1985
|
expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
|
|
@@ -2166,8 +2439,6 @@ describe('createHeadlessForm', () => {
|
|
|
2166
2439
|
});
|
|
2167
2440
|
|
|
2168
2441
|
describe('when a JSON Schema is provided', () => {
|
|
2169
|
-
const getByName = (fields, name) => fields.find((f) => f.name === name);
|
|
2170
|
-
|
|
2171
2442
|
describe('and all fields are optional', () => {
|
|
2172
2443
|
let handleValidation;
|
|
2173
2444
|
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
@@ -2574,23 +2845,23 @@ describe('createHeadlessForm', () => {
|
|
|
2574
2845
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2575
2846
|
field_a: null,
|
|
2576
2847
|
});
|
|
2577
|
-
expect(
|
|
2848
|
+
expect(getField(fields, 'field_b').isVisible).toBe(false);
|
|
2578
2849
|
});
|
|
2579
2850
|
|
|
2580
2851
|
it('given a match, runs "then" (turns visible and editable)', () => {
|
|
2581
2852
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2582
2853
|
initialValues: { field_a: 'yes' },
|
|
2583
2854
|
});
|
|
2584
|
-
expect(
|
|
2585
|
-
expect(
|
|
2855
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2856
|
+
expect(getField(fields, 'field_b').readOnly).toBe(false);
|
|
2586
2857
|
});
|
|
2587
2858
|
|
|
2588
2859
|
it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
|
|
2589
2860
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2590
2861
|
initialValues: { field_a: 'no' },
|
|
2591
2862
|
});
|
|
2592
|
-
expect(
|
|
2593
|
-
expect(
|
|
2863
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2864
|
+
expect(getField(fields, 'field_b').readOnly).toBe(true);
|
|
2594
2865
|
});
|
|
2595
2866
|
});
|
|
2596
2867
|
|
|
@@ -2602,26 +2873,26 @@ describe('createHeadlessForm', () => {
|
|
|
2602
2873
|
initialValues: { field_a: null, field_a_wrong: null },
|
|
2603
2874
|
});
|
|
2604
2875
|
// The dependent correct field gets hidden, but...
|
|
2605
|
-
expect(
|
|
2876
|
+
expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
|
|
2606
2877
|
// ...the dependent wrong field stays visible because the
|
|
2607
2878
|
// conditional is wrong (it's missing the if.required[])
|
|
2608
|
-
expect(
|
|
2879
|
+
expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
|
|
2609
2880
|
});
|
|
2610
2881
|
|
|
2611
2882
|
it('given a match ("yes"), both runs "then" (turn visible)', () => {
|
|
2612
2883
|
const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2613
2884
|
initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
|
|
2614
2885
|
});
|
|
2615
|
-
expect(
|
|
2616
|
-
expect(
|
|
2886
|
+
expect(getField(fieldsVisible, 'field_b').isVisible).toBe(true);
|
|
2887
|
+
expect(getField(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
|
|
2617
2888
|
});
|
|
2618
2889
|
|
|
2619
2890
|
it('not given a match ("no"), both run else (stay hidden)', () => {
|
|
2620
2891
|
const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2621
2892
|
initialValues: { field_a: 'no', field_a_wrong: 'no' },
|
|
2622
2893
|
});
|
|
2623
|
-
expect(
|
|
2624
|
-
expect(
|
|
2894
|
+
expect(getField(fieldsHidden, 'field_b').isVisible).toBe(false);
|
|
2895
|
+
expect(getField(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
|
|
2625
2896
|
});
|
|
2626
2897
|
});
|
|
2627
2898
|
|
|
@@ -2631,7 +2902,7 @@ describe('createHeadlessForm', () => {
|
|
|
2631
2902
|
field_a: 'no',
|
|
2632
2903
|
},
|
|
2633
2904
|
});
|
|
2634
|
-
const dependentField =
|
|
2905
|
+
const dependentField = getField(fields, 'field_b');
|
|
2635
2906
|
expect(dependentField.isVisible).toBe(false);
|
|
2636
2907
|
expect(dependentField.value).toBe(undefined);
|
|
2637
2908
|
});
|
|
@@ -2642,7 +2913,7 @@ describe('createHeadlessForm', () => {
|
|
|
2642
2913
|
field_a: 'yes',
|
|
2643
2914
|
},
|
|
2644
2915
|
});
|
|
2645
|
-
const dependentField =
|
|
2916
|
+
const dependentField = getField(fields, 'field_b');
|
|
2646
2917
|
expect(dependentField.isVisible).toBe(true);
|
|
2647
2918
|
expect(dependentField.value).toBe(undefined);
|
|
2648
2919
|
});
|