@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.
@@ -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 result = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
578
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
526
579
 
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
- ],
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 result = createHeadlessForm(schemaInputTypeSelectSolo);
612
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
555
613
 
556
- const fieldSelect = result.fields[0];
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 result = createHeadlessForm(schemaInputTypeRadioDeprecated);
733
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
670
734
 
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
- });
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
- const fieldValidator = result.fields[0].schema;
695
- expect(fieldValidator.isValidSync('yes')).toBe(true);
696
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
756
+ assertOptionsAllowed({
757
+ handleValidation,
758
+ fieldName: 'has_siblings',
759
+ validOptions: ['yes', 'no'],
760
+ });
697
761
  });
698
762
  it('support "radio" field type', () => {
699
- const result = createHeadlessForm(schemaInputTypeRadio);
763
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
700
764
 
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
- });
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
- const fieldValidator = result.fields[0].schema;
725
- expect(fieldValidator.isValidSync('yes')).toBe(true);
726
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
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 result = createHeadlessForm(schemaInputTypeRadioRequiredAndOptional);
794
+ const { fields, handleValidation } = createHeadlessForm(
795
+ schemaInputTypeRadioRequiredAndOptional
796
+ );
797
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
731
798
 
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
- ],
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
- const fieldValidator = result.fields[0].schema;
757
- expect(fieldValidator.isValidSync('yes')).toBe(true);
758
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
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
- 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
- ],
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
- it('supported "fieldset" with scoped conditionals', () => {
1340
- const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
1341
- const validateForm = (vals) => friendlyError(handleValidation(vals));
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
- // The "child.has_child" is required
1344
- expect(validateForm({})).toEqual({
1345
- child: {
1346
- has_child: 'Required field',
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
- // The "child.no" is valid
1351
- expect(
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: 'no',
1462
+ has_child: 'Required field',
1355
1463
  },
1356
- })
1357
- ).toBeUndefined();
1464
+ });
1358
1465
 
1359
- // Invalid because it expect child.age too
1360
- expect(
1361
- validateForm({
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
- has_child: 'yes',
1484
+ age: 'Required field',
1364
1485
  },
1365
- })
1366
- ).toEqual({
1367
- child: {
1368
- age: 'Required field',
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
- // Valid without optional child.passport_id
1373
- expect(
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
- // Valid with optional child.passport_id
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
- 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));
1521
+ await expect(validateForm(formValues)).toBeUndefined();
1522
+ expect(formValues.child.age).toBe(15);
1397
1523
 
1398
- const formValues = {
1399
- child: {
1400
- has_child: 'yes',
1401
- age: 15,
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
- await expect(validateForm(formValues)).toBeUndefined();
1406
- expect(formValues.child.age).toBe(15);
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
- formValues.child.has_child = 'no';
1409
- // form value updates re-validate; see computeYupSchema()
1410
- await expect(validateForm(formValues)).toBeUndefined();
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
- // when child.has_child is 'no' child.age is invisible
1413
- expect(formValues.child.age).toBe(null);
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 getByName = (fieldList, name) => fieldList.find((f) => f.name === name);
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 = getByName(fields, 'a_object');
1739
- const aFieldInTheFieldset = getByName(aFieldset.fields, 'foo');
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(getByName(fields, 'field_b').isVisible).toBe(false);
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(getByName(fields, 'field_b').isVisible).toBe(true);
2615
- expect(getByName(fields, 'field_b').readOnly).toBe(false);
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(getByName(fields, 'field_b').isVisible).toBe(true);
2623
- expect(getByName(fields, 'field_b').readOnly).toBe(true);
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(getByName(fieldsEmpty, 'field_b').isVisible).toBe(false);
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(getByName(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
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(getByName(fieldsVisible, 'field_b').isVisible).toBe(true);
2646
- expect(getByName(fieldsVisible, 'field_b_wrong').isVisible).toBe(true);
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(getByName(fieldsHidden, 'field_b').isVisible).toBe(false);
2654
- expect(getByName(fieldsHidden, 'field_b_wrong').isVisible).toBe(false);
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 = getByName(fields, 'field_b');
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 = getByName(fields, 'field_b');
2916
+ const dependentField = getField(fields, 'field_b');
2676
2917
  expect(dependentField.isVisible).toBe(true);
2677
2918
  expect(dependentField.value).toBe(undefined);
2678
2919
  });