@remoteoss/json-schema-form 0.4.0-beta.0 → 0.4.1-dev.20230703082439
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +55 -19
- package/dist/index.js +55 -19
- package/dist/standalone.js +410 -802
- package/package.json +1 -1
- package/src/tests/createHeadlessForm.test.js +507 -266
- package/src/tests/helpers.js +177 -45
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
schemaInputTypeRadioDeprecated,
|
|
11
11
|
schemaInputTypeRadio,
|
|
12
12
|
schemaInputTypeRadioRequiredAndOptional,
|
|
13
|
+
schemaInputRadioOptionalNull,
|
|
14
|
+
schemaInputRadioOptionalConventional,
|
|
13
15
|
schemaInputTypeRadioOptionsWithDetails,
|
|
14
16
|
schemaInputTypeSelectSoloDeprecated,
|
|
15
17
|
schemaInputTypeSelectSolo,
|
|
@@ -45,6 +47,7 @@ import {
|
|
|
45
47
|
mockNestedFieldset,
|
|
46
48
|
mockGroupArrayInput,
|
|
47
49
|
schemaFieldsetScopedCondition,
|
|
50
|
+
schemaWithConditionalToFieldset,
|
|
48
51
|
schemaWithConditionalPresentationProperties,
|
|
49
52
|
schemaWithConditionalReadOnlyProperty,
|
|
50
53
|
schemaWithWrongConditional,
|
|
@@ -77,6 +80,17 @@ function friendlyError({ formErrors }) {
|
|
|
77
80
|
return formErrors;
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
// Get a field by name recursively
|
|
84
|
+
// eg getField(demo, "age") -> returns "age" field
|
|
85
|
+
// eg getField(demo, child, name) -> returns "child.name" subfield
|
|
86
|
+
const getField = (fields, name, ...subNames) => {
|
|
87
|
+
const field = fields.find((f) => f.name === name);
|
|
88
|
+
if (subNames.length > 0) {
|
|
89
|
+
return getField(field.fields, ...subNames);
|
|
90
|
+
}
|
|
91
|
+
return field;
|
|
92
|
+
};
|
|
93
|
+
|
|
80
94
|
beforeEach(() => {
|
|
81
95
|
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
82
96
|
});
|
|
@@ -420,6 +434,45 @@ describe('createHeadlessForm', () => {
|
|
|
420
434
|
});
|
|
421
435
|
|
|
422
436
|
describe('field support', () => {
|
|
437
|
+
function assertOptionsAllowed({ handleValidation, fieldName, validOptions }) {
|
|
438
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
439
|
+
|
|
440
|
+
// All allowed options are valid
|
|
441
|
+
validOptions.forEach((value) => {
|
|
442
|
+
expect(
|
|
443
|
+
validateForm({
|
|
444
|
+
[fieldName]: value,
|
|
445
|
+
})
|
|
446
|
+
).toBeUndefined();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Any other arbitrary value is not valid.
|
|
450
|
+
expect(
|
|
451
|
+
validateForm({
|
|
452
|
+
[fieldName]: 'blah-blah',
|
|
453
|
+
})
|
|
454
|
+
).toEqual({
|
|
455
|
+
[fieldName]: 'The option "blah-blah" is not valid.',
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// This is a required field:
|
|
459
|
+
expect(validateForm({})).toEqual({
|
|
460
|
+
[fieldName]: 'Required field',
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// As required field, empty string ("") is also considered empty. @BUG RMT-518
|
|
464
|
+
// Expectation: The error to be "The option '' is not valid."
|
|
465
|
+
expect(validateForm({ [fieldName]: '' })).toEqual({
|
|
466
|
+
[fieldName]: 'Required field',
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// As required field, null is also considered empty @BUG RMT-518
|
|
470
|
+
// Expectation: The error to be "The option null is not valid."
|
|
471
|
+
expect(validateForm({ [fieldName]: null })).toEqual({
|
|
472
|
+
[fieldName]: 'Required field',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
423
476
|
it('support "text" field type', () => {
|
|
424
477
|
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
|
|
425
478
|
|
|
@@ -522,38 +575,43 @@ describe('createHeadlessForm', () => {
|
|
|
522
575
|
});
|
|
523
576
|
|
|
524
577
|
it('support "select" field type @deprecated', () => {
|
|
525
|
-
const
|
|
578
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
|
|
526
579
|
|
|
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
|
-
|
|
580
|
+
expect(fields).toMatchObject([
|
|
581
|
+
{
|
|
582
|
+
description: 'Life Insurance',
|
|
583
|
+
label: 'Benefits (solo)',
|
|
584
|
+
name: 'benefits',
|
|
585
|
+
placeholder: 'Select...',
|
|
586
|
+
type: 'select',
|
|
587
|
+
options: [
|
|
588
|
+
{
|
|
589
|
+
label: 'Medical Insurance',
|
|
590
|
+
value: 'Medical Insurance',
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
label: 'Health Insurance',
|
|
594
|
+
value: 'Health Insurance',
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
label: 'Travel Bonus',
|
|
598
|
+
value: 'Travel Bonus',
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
},
|
|
602
|
+
]);
|
|
603
|
+
|
|
604
|
+
assertOptionsAllowed({
|
|
605
|
+
handleValidation,
|
|
606
|
+
fieldName: 'benefits',
|
|
607
|
+
validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
|
|
551
608
|
});
|
|
552
609
|
});
|
|
610
|
+
|
|
553
611
|
it('support "select" field type', () => {
|
|
554
|
-
const
|
|
612
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
|
|
555
613
|
|
|
556
|
-
const fieldSelect =
|
|
614
|
+
const fieldSelect = fields[0];
|
|
557
615
|
expect(fieldSelect).toMatchObject({
|
|
558
616
|
name: 'browsers',
|
|
559
617
|
label: 'Browsers (solo)',
|
|
@@ -576,6 +634,12 @@ describe('createHeadlessForm', () => {
|
|
|
576
634
|
});
|
|
577
635
|
|
|
578
636
|
expect(fieldSelect).not.toHaveProperty('multiple');
|
|
637
|
+
|
|
638
|
+
assertOptionsAllowed({
|
|
639
|
+
handleValidation,
|
|
640
|
+
fieldName: 'browsers',
|
|
641
|
+
validOptions: ['chr', 'ff', 'ie'],
|
|
642
|
+
});
|
|
579
643
|
});
|
|
580
644
|
|
|
581
645
|
it('supports "select" field type with multiple options @deprecated', () => {
|
|
@@ -666,96 +730,149 @@ describe('createHeadlessForm', () => {
|
|
|
666
730
|
});
|
|
667
731
|
|
|
668
732
|
it('support "radio" field type @deprecated', () => {
|
|
669
|
-
const
|
|
733
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
|
|
670
734
|
|
|
671
|
-
expect(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
],
|
|
692
|
-
});
|
|
735
|
+
expect(fields).toMatchObject([
|
|
736
|
+
{
|
|
737
|
+
description: 'Do you have any siblings?',
|
|
738
|
+
label: 'Has siblings',
|
|
739
|
+
name: 'has_siblings',
|
|
740
|
+
options: [
|
|
741
|
+
{
|
|
742
|
+
label: 'Yes',
|
|
743
|
+
value: 'yes',
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
label: 'No',
|
|
747
|
+
value: 'no',
|
|
748
|
+
},
|
|
749
|
+
],
|
|
750
|
+
required: true,
|
|
751
|
+
schema: expect.any(Object),
|
|
752
|
+
type: 'radio',
|
|
753
|
+
},
|
|
754
|
+
]);
|
|
693
755
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
756
|
+
assertOptionsAllowed({
|
|
757
|
+
handleValidation,
|
|
758
|
+
fieldName: 'has_siblings',
|
|
759
|
+
validOptions: ['yes', 'no'],
|
|
760
|
+
});
|
|
697
761
|
});
|
|
698
762
|
it('support "radio" field type', () => {
|
|
699
|
-
const
|
|
763
|
+
const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
|
|
700
764
|
|
|
701
|
-
expect(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
],
|
|
722
|
-
});
|
|
765
|
+
expect(fields).toMatchObject([
|
|
766
|
+
{
|
|
767
|
+
description: 'Do you have any siblings?',
|
|
768
|
+
label: 'Has siblings',
|
|
769
|
+
name: 'has_siblings',
|
|
770
|
+
options: [
|
|
771
|
+
{
|
|
772
|
+
label: 'Yes',
|
|
773
|
+
value: 'yes',
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
label: 'No',
|
|
777
|
+
value: 'no',
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
required: true,
|
|
781
|
+
schema: expect.any(Object),
|
|
782
|
+
type: 'radio',
|
|
783
|
+
},
|
|
784
|
+
]);
|
|
723
785
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
786
|
+
assertOptionsAllowed({
|
|
787
|
+
handleValidation,
|
|
788
|
+
fieldName: 'has_siblings',
|
|
789
|
+
validOptions: ['yes', 'no'],
|
|
790
|
+
});
|
|
727
791
|
});
|
|
728
792
|
|
|
729
793
|
it('support "radio" optional field', () => {
|
|
730
|
-
const
|
|
794
|
+
const { fields, handleValidation } = createHeadlessForm(
|
|
795
|
+
schemaInputTypeRadioRequiredAndOptional
|
|
796
|
+
);
|
|
797
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
731
798
|
|
|
732
|
-
expect(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
799
|
+
expect(fields).toMatchObject([
|
|
800
|
+
{},
|
|
801
|
+
{
|
|
802
|
+
name: 'has_car',
|
|
803
|
+
label: 'Has car',
|
|
804
|
+
description: 'Do you have a car? (optional field, check oneOf)',
|
|
805
|
+
options: [
|
|
806
|
+
{
|
|
807
|
+
label: 'Yes',
|
|
808
|
+
value: 'yes',
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
label: 'No',
|
|
812
|
+
value: 'no',
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
required: false,
|
|
816
|
+
schema: expect.any(Object),
|
|
817
|
+
type: 'radio',
|
|
818
|
+
},
|
|
819
|
+
]);
|
|
820
|
+
|
|
821
|
+
expect(
|
|
822
|
+
validateForm({
|
|
823
|
+
has_siblings: 'yes',
|
|
824
|
+
has_car: 'yes',
|
|
825
|
+
})
|
|
826
|
+
).toBeUndefined();
|
|
827
|
+
expect(validateForm({})).toEqual({
|
|
828
|
+
has_siblings: 'Required field',
|
|
754
829
|
});
|
|
830
|
+
});
|
|
755
831
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
832
|
+
describe('support "radio" optional field - more examples @BUG RMT-518', () => {
|
|
833
|
+
function assertCommonBehavior(validateForm) {
|
|
834
|
+
// Happy path
|
|
835
|
+
expect(validateForm({ has_car: 'yes' })).toBeUndefined();
|
|
836
|
+
|
|
837
|
+
// Accepts undefined field
|
|
838
|
+
expect(validateForm({})).toBeUndefined();
|
|
839
|
+
|
|
840
|
+
// Does not accept other values
|
|
841
|
+
expect(validateForm({ has_car: 'blah-blah' })).toEqual({
|
|
842
|
+
has_car: 'The option "blah-blah" is not valid.',
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Does not accept "null" as string
|
|
846
|
+
expect(validateForm({ has_car: 'null' })).toEqual({
|
|
847
|
+
has_car: 'The option "null" is not valid.',
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// Accepts empty string ("") — @BUG RMT-518
|
|
851
|
+
// Expectation: Does not accept empty string ("")
|
|
852
|
+
expect(validateForm({ has_car: '' })).toBeUndefined();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
it('with null option (as Remote)', () => {
|
|
856
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
|
|
857
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
858
|
+
|
|
859
|
+
assertCommonBehavior(validateForm);
|
|
860
|
+
|
|
861
|
+
// Accepts null value
|
|
862
|
+
expect(validateForm({ has_car: null })).toBeUndefined();
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('without null option (conventional way)', () => {
|
|
866
|
+
const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
|
|
867
|
+
const validateForm = (vals) => friendlyError(handleValidation(vals));
|
|
868
|
+
|
|
869
|
+
assertCommonBehavior(validateForm);
|
|
870
|
+
|
|
871
|
+
// Does NOT accept null value
|
|
872
|
+
expect(validateForm({ has_car: null })).toEqual({
|
|
873
|
+
has_car: 'The option null is not valid.',
|
|
874
|
+
});
|
|
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
|
+
});
|
|
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
|
+
});
|
|
1404
1638
|
|
|
1405
|
-
|
|
1406
|
-
|
|
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();
|
|
1407
1644
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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
|
+
);
|
|
1411
1649
|
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
});
|