@remoteoss/json-schema-form 0.4.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 +7 -0
- package/dist/index.cjs +59 -21
- package/dist/index.js +59 -21
- package/dist/standalone.js +414 -804
- package/package.json +1 -1
- package/src/tests/createHeadlessForm.test.js +507 -266
- package/src/tests/helpers.custom.js +1 -1
- 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,37 @@ 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(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
|
+
|
|
423
468
|
it('support "text" field type', () => {
|
|
424
469
|
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
425
470
|
|
|
@@ -522,38 +567,43 @@ describe('createHeadlessForm', () => {
|
|
|
522
567
|
});
|
|
523
568
|
|
|
524
569
|
it('support "select" field type @deprecated', () => {
|
|
525
|
-
const
|
|
570
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
|
|
526
571
|
|
|
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
|
-
|
|
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'],
|
|
551
600
|
});
|
|
552
601
|
});
|
|
602
|
+
|
|
553
603
|
it('support "select" field type', () => {
|
|
554
|
-
const
|
|
604
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
555
605
|
|
|
556
|
-
const fieldSelect =
|
|
606
|
+
const fieldSelect = fields[0];
|
|
557
607
|
expect(fieldSelect).toMatchObject({
|
|
558
608
|
name: 'browsers',
|
|
559
609
|
label: 'Browsers (solo)',
|
|
@@ -576,6 +626,12 @@ describe('createHeadlessForm', () => {
|
|
|
576
626
|
});
|
|
577
627
|
|
|
578
628
|
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
629
|
+
|
|
630
|
+
assertOptionsAllowed({
|
|
631
|
+
handleValidation,
|
|
632
|
+
fieldName: 'browsers',
|
|
633
|
+
validOptions: ['chr', 'ff', 'ie'],
|
|
634
|
+
});
|
|
579
635
|
});
|
|
580
636
|
|
|
581
637
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
@@ -666,96 +722,157 @@ describe('createHeadlessForm', () => {
|
|
|
666
722
|
});
|
|
667
723
|
|
|
668
724
|
it('support "radio" field type @deprecated', () => {
|
|
669
|
-
const
|
|
725
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
670
726
|
|
|
671
|
-
expect(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
],
|
|
692
|
-
});
|
|
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
|
+
]);
|
|
693
747
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
748
|
+
assertOptionsAllowed({
|
|
749
|
+
handleValidation,
|
|
750
|
+
fieldName: 'has_siblings',
|
|
751
|
+
validOptions: ['yes', 'no'],
|
|
752
|
+
});
|
|
697
753
|
});
|
|
698
754
|
it('support "radio" field type', () => {
|
|
699
|
-
const
|
|
755
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
|
|
700
756
|
|
|
701
|
-
expect(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
],
|
|
722
|
-
});
|
|
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
|
+
]);
|
|
723
777
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
778
|
+
assertOptionsAllowed({
|
|
779
|
+
handleValidation,
|
|
780
|
+
fieldName: 'has_siblings',
|
|
781
|
+
validOptions: ['yes', 'no'],
|
|
782
|
+
});
|
|
727
783
|
});
|
|
728
784
|
|
|
729
785
|
it('support "radio" optional field', () => {
|
|
730
|
-
const
|
|
786
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
787
|
+
schemaInputTypeRadioRequiredAndOptional
|
|
788
|
+
);
|
|
789
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
731
790
|
|
|
732
|
-
expect(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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',
|
|
754
821
|
});
|
|
822
|
+
});
|
|
755
823
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
+
});
|
|
759
876
|
});
|
|
760
877
|
|
|
761
878
|
it('support "radio" field type with extra info inside each option', () => {
|
|
@@ -1182,46 +1299,6 @@ describe('createHeadlessForm', () => {
|
|
|
1182
1299
|
});
|
|
1183
1300
|
});
|
|
1184
1301
|
|
|
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
1302
|
it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
|
|
1226
1303
|
const result = createHeadlessForm(
|
|
1227
1304
|
JSONSchemaBuilder()
|
|
@@ -1287,130 +1364,298 @@ describe('createHeadlessForm', () => {
|
|
|
1287
1364
|
});
|
|
1288
1365
|
});
|
|
1289
1366
|
|
|
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
|
-
],
|
|
1367
|
+
describe('supports "fieldset" field type', () => {
|
|
1368
|
+
it('supports basic case', () => {
|
|
1369
|
+
const result = createHeadlessForm({
|
|
1370
|
+
properties: {
|
|
1371
|
+
fieldset: mockFieldset,
|
|
1334
1372
|
},
|
|
1335
|
-
|
|
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
|
+
});
|
|
1336
1404
|
});
|
|
1337
|
-
});
|
|
1338
1405
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1406
|
+
it('supports nested fieldset (fieldset inside fieldset)', () => {
|
|
1407
|
+
const result = createHeadlessForm(
|
|
1408
|
+
JSONSchemaBuilder()
|
|
1409
|
+
.addInput({
|
|
1410
|
+
nestedFieldset: mockNestedFieldset,
|
|
1411
|
+
})
|
|
1412
|
+
.build()
|
|
1413
|
+
);
|
|
1342
1414
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
+
});
|
|
1348
1453
|
});
|
|
1349
1454
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
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({
|
|
1353
1461
|
child: {
|
|
1354
|
-
has_child: '
|
|
1462
|
+
has_child: 'Required field',
|
|
1355
1463
|
},
|
|
1356
|
-
})
|
|
1357
|
-
).toBeUndefined();
|
|
1464
|
+
});
|
|
1358
1465
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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({
|
|
1362
1483
|
child: {
|
|
1363
|
-
|
|
1484
|
+
age: 'Required field',
|
|
1364
1485
|
},
|
|
1365
|
-
})
|
|
1366
|
-
|
|
1367
|
-
child
|
|
1368
|
-
|
|
1369
|
-
|
|
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();
|
|
1370
1508
|
});
|
|
1371
1509
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
validateForm(
|
|
1375
|
-
child: {
|
|
1376
|
-
has_child: 'yes',
|
|
1377
|
-
age: 15,
|
|
1378
|
-
},
|
|
1379
|
-
})
|
|
1380
|
-
).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));
|
|
1381
1513
|
|
|
1382
|
-
|
|
1383
|
-
expect(
|
|
1384
|
-
validateForm({
|
|
1514
|
+
const formValues = {
|
|
1385
1515
|
child: {
|
|
1386
1516
|
has_child: 'yes',
|
|
1387
1517
|
age: 15,
|
|
1388
|
-
passport_id: 'asdf',
|
|
1389
1518
|
},
|
|
1390
|
-
}
|
|
1391
|
-
).toBeUndefined();
|
|
1392
|
-
});
|
|
1519
|
+
};
|
|
1393
1520
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
1521
|
+
await expect(validateForm(formValues)).toBeUndefined();
|
|
1522
|
+
expect(formValues.child.age).toBe(15);
|
|
1397
1523
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
+
});
|
|
1404
1586
|
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
+
});
|
|
1407
1605
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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
|
+
);
|
|
1411
1612
|
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
+
});
|
|
1638
|
+
|
|
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();
|
|
1644
|
+
|
|
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
|
+
);
|
|
1649
|
+
|
|
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
|
+
});
|
|
1414
1659
|
});
|
|
1415
1660
|
|
|
1416
1661
|
it('support "email" field type', () => {
|
|
@@ -1729,14 +1974,12 @@ describe('createHeadlessForm', () => {
|
|
|
1729
1974
|
strictInputType: false,
|
|
1730
1975
|
});
|
|
1731
1976
|
|
|
1732
|
-
const
|
|
1733
|
-
|
|
1734
|
-
const aFieldInRoot = getByName(fields, 'a_string');
|
|
1977
|
+
const aFieldInRoot = getField(fields, 'a_string');
|
|
1735
1978
|
// It's the entire json schema
|
|
1736
1979
|
expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
|
|
1737
1980
|
|
|
1738
|
-
const aFieldset =
|
|
1739
|
-
const aFieldInTheFieldset =
|
|
1981
|
+
const aFieldset = getField(fields, 'a_object');
|
|
1982
|
+
const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
|
|
1740
1983
|
|
|
1741
1984
|
// It's only the json schema of that fieldset
|
|
1742
1985
|
expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
|
|
@@ -2196,8 +2439,6 @@ describe('createHeadlessForm', () => {
|
|
|
2196
2439
|
});
|
|
2197
2440
|
|
|
2198
2441
|
describe('when a JSON Schema is provided', () => {
|
|
2199
|
-
const getByName = (fields, name) => fields.find((f) => f.name === name);
|
|
2200
|
-
|
|
2201
2442
|
describe('and all fields are optional', () => {
|
|
2202
2443
|
let handleValidation;
|
|
2203
2444
|
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
@@ -2604,23 +2845,23 @@ describe('createHeadlessForm', () => {
|
|
|
2604
2845
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2605
2846
|
field_a: null,
|
|
2606
2847
|
});
|
|
2607
|
-
expect(
|
|
2848
|
+
expect(getField(fields, 'field_b').isVisible).toBe(false);
|
|
2608
2849
|
});
|
|
2609
2850
|
|
|
2610
2851
|
it('given a match, runs "then" (turns visible and editable)', () => {
|
|
2611
2852
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2612
2853
|
initialValues: { field_a: 'yes' },
|
|
2613
2854
|
});
|
|
2614
|
-
expect(
|
|
2615
|
-
expect(
|
|
2855
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2856
|
+
expect(getField(fields, 'field_b').readOnly).toBe(false);
|
|
2616
2857
|
});
|
|
2617
2858
|
|
|
2618
2859
|
it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
|
|
2619
2860
|
const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
|
|
2620
2861
|
initialValues: { field_a: 'no' },
|
|
2621
2862
|
});
|
|
2622
|
-
expect(
|
|
2623
|
-
expect(
|
|
2863
|
+
expect(getField(fields, 'field_b').isVisible).toBe(true);
|
|
2864
|
+
expect(getField(fields, 'field_b').readOnly).toBe(true);
|
|
2624
2865
|
});
|
|
2625
2866
|
});
|
|
2626
2867
|
|
|
@@ -2632,26 +2873,26 @@ describe('createHeadlessForm', () => {
|
|
|
2632
2873
|
initialValues: { field_a: null, field_a_wrong: null },
|
|
2633
2874
|
});
|
|
2634
2875
|
// The dependent correct field gets hidden, but...
|
|
2635
|
-
expect(
|
|
2876
|
+
expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
|
|
2636
2877
|
// ...the dependent wrong field stays visible because the
|
|
2637
2878
|
// conditional is wrong (it's missing the if.required[])
|
|
2638
|
-
expect(
|
|
2879
|
+
expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
|
|
2639
2880
|
});
|
|
2640
2881
|
|
|
2641
2882
|
it('given a match ("yes"), both runs "then" (turn visible)', () => {
|
|
2642
2883
|
const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2643
2884
|
initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
|
|
2644
2885
|
});
|
|
2645
|
-
expect(
|
|
2646
|
-
expect(
|
|
2886
|
+
expect(getField(fieldsVisible, 'field_b').isVisible).toBe(true);
|
|
2887
|
+
expect(getField(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
|
|
2647
2888
|
});
|
|
2648
2889
|
|
|
2649
2890
|
it('not given a match ("no"), both run else (stay hidden)', () => {
|
|
2650
2891
|
const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
|
|
2651
2892
|
initialValues: { field_a: 'no', field_a_wrong: 'no' },
|
|
2652
2893
|
});
|
|
2653
|
-
expect(
|
|
2654
|
-
expect(
|
|
2894
|
+
expect(getField(fieldsHidden, 'field_b').isVisible).toBe(false);
|
|
2895
|
+
expect(getField(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
|
|
2655
2896
|
});
|
|
2656
2897
|
});
|
|
2657
2898
|
|
|
@@ -2661,7 +2902,7 @@ describe('createHeadlessForm', () => {
|
|
|
2661
2902
|
field_a: 'no',
|
|
2662
2903
|
},
|
|
2663
2904
|
});
|
|
2664
|
-
const dependentField =
|
|
2905
|
+
const dependentField = getField(fields, 'field_b');
|
|
2665
2906
|
expect(dependentField.isVisible).toBe(false);
|
|
2666
2907
|
expect(dependentField.value).toBe(undefined);
|
|
2667
2908
|
});
|
|
@@ -2672,7 +2913,7 @@ describe('createHeadlessForm', () => {
|
|
|
2672
2913
|
field_a: 'yes',
|
|
2673
2914
|
},
|
|
2674
2915
|
});
|
|
2675
|
-
const dependentField =
|
|
2916
|
+
const dependentField = getField(fields, 'field_b');
|
|
2676
2917
|
expect(dependentField.isVisible).toBe(true);
|
|
2677
2918
|
expect(dependentField.value).toBe(undefined);
|
|
2678
2919
|
});
|