@remoteoss/json-schema-form 0.4.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,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,42 @@ 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
+ expect(
465
+ validateForm({
466
+ [fieldName]: '',
467
+ })
468
+ ).toEqual({
469
+ [fieldName]: 'Required field',
470
+ });
471
+ }
472
+
423
473
  it('support "text" field type', () => {
424
474
  const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
425
475
 
@@ -522,38 +572,43 @@ describe('createHeadlessForm', () => {
522
572
  });
523
573
 
524
574
  it('support "select" field type @deprecated', () => {
525
- const result = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
575
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
526
576
 
527
- expect(result).toMatchObject({
528
- fields: [
529
- {
530
- description: 'Life Insurance',
531
- label: 'Benefits (solo)',
532
- name: 'benefits',
533
- placeholder: 'Select...',
534
- type: 'select',
535
- options: [
536
- {
537
- label: 'Medical Insurance',
538
- value: 'Medical Insurance',
539
- },
540
- {
541
- label: 'Health Insurance',
542
- value: 'Health Insurance',
543
- },
544
- {
545
- label: 'Travel Bonus',
546
- value: 'Travel Bonus',
547
- },
548
- ],
549
- },
550
- ],
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'],
551
605
  });
552
606
  });
607
+
553
608
  it('support "select" field type', () => {
554
- const result = createHeadlessForm(schemaInputTypeSelectSolo);
609
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
555
610
 
556
- const fieldSelect = result.fields[0];
611
+ const fieldSelect = fields[0];
557
612
  expect(fieldSelect).toMatchObject({
558
613
  name: 'browsers',
559
614
  label: 'Browsers (solo)',
@@ -576,6 +631,12 @@ describe('createHeadlessForm', () => {
576
631
  });
577
632
 
578
633
  expect(fieldSelect).not.toHaveProperty('multiple');
634
+
635
+ assertOptionsAllowed({
636
+ handleValidation,
637
+ fieldName: 'browsers',
638
+ validOptions: ['chr', 'ff', 'ie'],
639
+ });
579
640
  });
580
641
 
581
642
  it('supports "select" field type with multiple options @deprecated', () => {
@@ -666,96 +727,149 @@ describe('createHeadlessForm', () => {
666
727
  });
667
728
 
668
729
  it('support "radio" field type @deprecated', () => {
669
- const result = createHeadlessForm(schemaInputTypeRadioDeprecated);
730
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
670
731
 
671
- expect(result).toMatchObject({
672
- fields: [
673
- {
674
- description: 'Do you have any siblings?',
675
- label: 'Has siblings',
676
- name: 'has_siblings',
677
- options: [
678
- {
679
- label: 'Yes',
680
- value: 'yes',
681
- },
682
- {
683
- label: 'No',
684
- value: 'no',
685
- },
686
- ],
687
- required: true,
688
- schema: expect.any(Object),
689
- type: 'radio',
690
- },
691
- ],
692
- });
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
+ ]);
693
752
 
694
- const fieldValidator = result.fields[0].schema;
695
- expect(fieldValidator.isValidSync('yes')).toBe(true);
696
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
753
+ assertOptionsAllowed({
754
+ handleValidation,
755
+ fieldName: 'has_siblings',
756
+ validOptions: ['yes', 'no'],
757
+ });
697
758
  });
698
759
  it('support "radio" field type', () => {
699
- const result = createHeadlessForm(schemaInputTypeRadio);
760
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
700
761
 
701
- expect(result).toMatchObject({
702
- fields: [
703
- {
704
- description: 'Do you have any siblings?',
705
- label: 'Has siblings',
706
- name: 'has_siblings',
707
- options: [
708
- {
709
- label: 'Yes',
710
- value: 'yes',
711
- },
712
- {
713
- label: 'No',
714
- value: 'no',
715
- },
716
- ],
717
- required: true,
718
- schema: expect.any(Object),
719
- type: 'radio',
720
- },
721
- ],
722
- });
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
+ ]);
723
782
 
724
- const fieldValidator = result.fields[0].schema;
725
- expect(fieldValidator.isValidSync('yes')).toBe(true);
726
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
783
+ assertOptionsAllowed({
784
+ handleValidation,
785
+ fieldName: 'has_siblings',
786
+ validOptions: ['yes', 'no'],
787
+ });
727
788
  });
728
789
 
729
790
  it('support "radio" optional field', () => {
730
- const result = createHeadlessForm(schemaInputTypeRadioRequiredAndOptional);
791
+ const { fields, handleValidation } = createHeadlessForm(
792
+ schemaInputTypeRadioRequiredAndOptional
793
+ );
794
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
731
795
 
732
- expect(result).toMatchObject({
733
- fields: [
734
- {},
735
- {
736
- name: 'has_car',
737
- label: 'Has car',
738
- description: 'Do you have a car? (optional field, check oneOf)',
739
- options: [
740
- {
741
- label: 'Yes',
742
- value: 'yes',
743
- },
744
- {
745
- label: 'No',
746
- value: 'no',
747
- },
748
- ],
749
- required: false,
750
- schema: expect.any(Object),
751
- type: 'radio',
752
- },
753
- ],
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',
754
826
  });
827
+ });
755
828
 
756
- const fieldValidator = result.fields[0].schema;
757
- expect(fieldValidator.isValidSync('yes')).toBe(true);
758
- 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
+ });
759
873
  });
760
874
 
761
875
  it('support "radio" field type with extra info inside each option', () => {
@@ -1182,46 +1296,6 @@ describe('createHeadlessForm', () => {
1182
1296
  });
1183
1297
  });
1184
1298
 
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
1299
  it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
1226
1300
  const result = createHeadlessForm(
1227
1301
  JSONSchemaBuilder()
@@ -1287,130 +1361,298 @@ describe('createHeadlessForm', () => {
1287
1361
  });
1288
1362
  });
1289
1363
 
1290
- it('supports nested "fieldset" field type', () => {
1291
- const result = createHeadlessForm(
1292
- JSONSchemaBuilder()
1293
- .addInput({
1294
- nestedFieldset: mockNestedFieldset,
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
- ],
1364
+ describe('supports "fieldset" field type', () => {
1365
+ it('supports basic case', () => {
1366
+ const result = createHeadlessForm({
1367
+ properties: {
1368
+ fieldset: mockFieldset,
1334
1369
  },
1335
- ],
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
+ });
1336
1401
  });
1337
- });
1338
1402
 
1339
- it('supported "fieldset" with scoped conditionals', () => {
1340
- const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
1341
- 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
+ );
1342
1411
 
1343
- // The "child.has_child" is required
1344
- expect(validateForm({})).toEqual({
1345
- child: {
1346
- has_child: 'Required field',
1347
- },
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
+ });
1348
1450
  });
1349
1451
 
1350
- // The "child.no" is valid
1351
- expect(
1352
- 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({
1353
1458
  child: {
1354
- has_child: 'no',
1459
+ has_child: 'Required field',
1355
1460
  },
1356
- })
1357
- ).toBeUndefined();
1461
+ });
1358
1462
 
1359
- // Invalid because it expect child.age too
1360
- expect(
1361
- 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({
1362
1480
  child: {
1363
- has_child: 'yes',
1481
+ age: 'Required field',
1364
1482
  },
1365
- })
1366
- ).toEqual({
1367
- child: {
1368
- age: 'Required field',
1369
- },
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();
1370
1505
  });
1371
1506
 
1372
- // Valid without optional child.passport_id
1373
- expect(
1374
- validateForm({
1375
- child: {
1376
- has_child: 'yes',
1377
- age: 15,
1378
- },
1379
- })
1380
- ).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));
1381
1510
 
1382
- // Valid with optional child.passport_id
1383
- expect(
1384
- validateForm({
1511
+ const formValues = {
1385
1512
  child: {
1386
1513
  has_child: 'yes',
1387
1514
  age: 15,
1388
- passport_id: 'asdf',
1389
1515
  },
1390
- })
1391
- ).toBeUndefined();
1392
- });
1516
+ };
1393
1517
 
1394
- it('should set any nested "fieldset" form values to null when they are invisible', async () => {
1395
- const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
1396
- const validateForm = (vals) => friendlyError(handleValidation(vals));
1518
+ await expect(validateForm(formValues)).toBeUndefined();
1519
+ expect(formValues.child.age).toBe(15);
1397
1520
 
1398
- const formValues = {
1399
- child: {
1400
- has_child: 'yes',
1401
- age: 15,
1402
- },
1403
- };
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)
1537
+
1538
+ // Setup (arrange)
1539
+ const { fields, handleValidation } = createHeadlessForm(schemaWithConditionalToFieldset);
1540
+
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
+ });
1583
+
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
+ });
1404
1635
 
1405
- await expect(validateForm(formValues)).toBeUndefined();
1406
- expect(formValues.child.age).toBe(15);
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();
1407
1641
 
1408
- formValues.child.has_child = 'no';
1409
- // form value updates re-validate; see computeYupSchema()
1410
- await expect(validateForm(formValues)).toBeUndefined();
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
+ );
1411
1646
 
1412
- // when child.has_child is 'no' child.age is invisible
1413
- expect(formValues.child.age).toBe(null);
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
+ });
1414
1656
  });
1415
1657
 
1416
1658
  it('support "email" field type', () => {
@@ -1729,14 +1971,12 @@ describe('createHeadlessForm', () => {
1729
1971
  strictInputType: false,
1730
1972
  });
1731
1973
 
1732
- const getByName = (fieldList, name) => fieldList.find((f) => f.name === name);
1733
-
1734
- const aFieldInRoot = getByName(fields, 'a_string');
1974
+ const aFieldInRoot = getField(fields, 'a_string');
1735
1975
  // It's the entire json schema
1736
1976
  expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
1737
1977
 
1738
- const aFieldset = getByName(fields, 'a_object');
1739
- const aFieldInTheFieldset = getByName(aFieldset.fields, 'foo');
1978
+ const aFieldset = getField(fields, 'a_object');
1979
+ const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
1740
1980
 
1741
1981
  // It's only the json schema of that fieldset
1742
1982
  expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
@@ -2196,8 +2436,6 @@ describe('createHeadlessForm', () => {
2196
2436
  });
2197
2437
 
2198
2438
  describe('when a JSON Schema is provided', () => {
2199
- const getByName = (fields, name) => fields.find((f) => f.name === name);
2200
-
2201
2439
  describe('and all fields are optional', () => {
2202
2440
  let handleValidation;
2203
2441
  const validateForm = (vals) => friendlyError(handleValidation(vals));
@@ -2604,23 +2842,23 @@ describe('createHeadlessForm', () => {
2604
2842
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2605
2843
  field_a: null,
2606
2844
  });
2607
- expect(getByName(fields, 'field_b').isVisible).toBe(false);
2845
+ expect(getField(fields, 'field_b').isVisible).toBe(false);
2608
2846
  });
2609
2847
 
2610
2848
  it('given a match, runs "then" (turns visible and editable)', () => {
2611
2849
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2612
2850
  initialValues: { field_a: 'yes' },
2613
2851
  });
2614
- expect(getByName(fields, 'field_b').isVisible).toBe(true);
2615
- 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);
2616
2854
  });
2617
2855
 
2618
2856
  it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
2619
2857
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2620
2858
  initialValues: { field_a: 'no' },
2621
2859
  });
2622
- expect(getByName(fields, 'field_b').isVisible).toBe(true);
2623
- 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);
2624
2862
  });
2625
2863
  });
2626
2864
 
@@ -2632,26 +2870,26 @@ describe('createHeadlessForm', () => {
2632
2870
  initialValues: { field_a: null, field_a_wrong: null },
2633
2871
  });
2634
2872
  // The dependent correct field gets hidden, but...
2635
- expect(getByName(fieldsEmpty, 'field_b').isVisible).toBe(false);
2873
+ expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
2636
2874
  // ...the dependent wrong field stays visible because the
2637
2875
  // conditional is wrong (it's missing the if.required[])
2638
- expect(getByName(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
2876
+ expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
2639
2877
  });
2640
2878
 
2641
2879
  it('given a match ("yes"), both runs "then" (turn visible)', () => {
2642
2880
  const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
2643
2881
  initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
2644
2882
  });
2645
- expect(getByName(fieldsVisible, 'field_b').isVisible).toBe(true);
2646
- 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);
2647
2885
  });
2648
2886
 
2649
2887
  it('not given a match ("no"), both run else (stay hidden)', () => {
2650
2888
  const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
2651
2889
  initialValues: { field_a: 'no', field_a_wrong: 'no' },
2652
2890
  });
2653
- expect(getByName(fieldsHidden, 'field_b').isVisible).toBe(false);
2654
- 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);
2655
2893
  });
2656
2894
  });
2657
2895
 
@@ -2661,7 +2899,7 @@ describe('createHeadlessForm', () => {
2661
2899
  field_a: 'no',
2662
2900
  },
2663
2901
  });
2664
- const dependentField = getByName(fields, 'field_b');
2902
+ const dependentField = getField(fields, 'field_b');
2665
2903
  expect(dependentField.isVisible).toBe(false);
2666
2904
  expect(dependentField.value).toBe(undefined);
2667
2905
  });
@@ -2672,7 +2910,7 @@ describe('createHeadlessForm', () => {
2672
2910
  field_a: 'yes',
2673
2911
  },
2674
2912
  });
2675
- const dependentField = getByName(fields, 'field_b');
2913
+ const dependentField = getField(fields, 'field_b');
2676
2914
  expect(dependentField.isVisible).toBe(true);
2677
2915
  expect(dependentField.value).toBe(undefined);
2678
2916
  });