@remoteoss/json-schema-form 0.3.0-beta.0 → 0.4.1-dev.20230702181843

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