@remoteoss/json-schema-form 0.4.0-beta.0 → 0.4.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,8 @@ import {
10
10
  schemaInputTypeRadioDeprecated,
11
11
  schemaInputTypeRadio,
12
12
  schemaInputTypeRadioRequiredAndOptional,
13
+ schemaInputRadioOptionalNull,
14
+ schemaInputRadioOptionalConventional,
13
15
  schemaInputTypeRadioOptionsWithDetails,
14
16
  schemaInputTypeSelectSoloDeprecated,
15
17
  schemaInputTypeSelectSolo,
@@ -45,6 +47,7 @@ import {
45
47
  mockNestedFieldset,
46
48
  mockGroupArrayInput,
47
49
  schemaFieldsetScopedCondition,
50
+ schemaWithConditionalToFieldset,
48
51
  schemaWithConditionalPresentationProperties,
49
52
  schemaWithConditionalReadOnlyProperty,
50
53
  schemaWithWrongConditional,
@@ -77,6 +80,17 @@ function friendlyError({ formErrors }) {
77
80
  return formErrors;
78
81
  }
79
82
 
83
+ // Get a field by name recursively
84
+ // eg getField(demo, "age") -> returns "age" field
85
+ // eg getField(demo, child, name) -> returns "child.name" subfield
86
+ const getField = (fields, name, ...subNames) => {
87
+ const field = fields.find((f) => f.name === name);
88
+ if (subNames.length > 0) {
89
+ return getField(field.fields, ...subNames);
90
+ }
91
+ return field;
92
+ };
93
+
80
94
  beforeEach(() => {
81
95
  jest.spyOn(console, 'error').mockImplementation(() => {});
82
96
  });
@@ -420,6 +434,37 @@ describe('createHeadlessForm', () => {
420
434
  });
421
435
 
422
436
  describe('field support', () => {
437
+ function assertOptionsAllowed({ handleValidation, fieldName, validOptions }) {
438
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
439
+
440
+ // All allowed options are valid
441
+ validOptions.forEach((value) => {
442
+ expect(validateForm({ [fieldName]: value })).toBeUndefined();
443
+ });
444
+
445
+ // Any other arbitrary value is not valid.
446
+ expect(validateForm({ [fieldName]: 'blah-blah' })).toEqual({
447
+ [fieldName]: 'The option "blah-blah" is not valid.',
448
+ });
449
+
450
+ // Given undefined, it says it's a required field.
451
+ expect(validateForm({})).toEqual({
452
+ [fieldName]: 'Required field',
453
+ });
454
+
455
+ // As required field, empty string ("") is also considered empty. @BUG RMT-518
456
+ // Expectation: The error to be "The option '' is not valid."
457
+ expect(validateForm({ [fieldName]: '' })).toEqual({
458
+ [fieldName]: 'Required field',
459
+ });
460
+
461
+ // As required field, null is also considered empty @BUG RMT-518
462
+ // Expectation: The error to be "The option null is not valid."
463
+ expect(validateForm({ [fieldName]: null })).toEqual({
464
+ [fieldName]: 'Required field',
465
+ });
466
+ }
467
+
423
468
  it('support "text" field type', () => {
424
469
  const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
425
470
 
@@ -522,38 +567,43 @@ describe('createHeadlessForm', () => {
522
567
  });
523
568
 
524
569
  it('support "select" field type @deprecated', () => {
525
- const result = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
570
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
526
571
 
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
- ],
572
+ expect(fields).toMatchObject([
573
+ {
574
+ description: 'Life Insurance',
575
+ label: 'Benefits (solo)',
576
+ name: 'benefits',
577
+ placeholder: 'Select...',
578
+ type: 'select',
579
+ options: [
580
+ {
581
+ label: 'Medical Insurance',
582
+ value: 'Medical Insurance',
583
+ },
584
+ {
585
+ label: 'Health Insurance',
586
+ value: 'Health Insurance',
587
+ },
588
+ {
589
+ label: 'Travel Bonus',
590
+ value: 'Travel Bonus',
591
+ },
592
+ ],
593
+ },
594
+ ]);
595
+
596
+ assertOptionsAllowed({
597
+ handleValidation,
598
+ fieldName: 'benefits',
599
+ validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
551
600
  });
552
601
  });
602
+
553
603
  it('support "select" field type', () => {
554
- const result = createHeadlessForm(schemaInputTypeSelectSolo);
604
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
555
605
 
556
- const fieldSelect = result.fields[0];
606
+ const fieldSelect = fields[0];
557
607
  expect(fieldSelect).toMatchObject({
558
608
  name: 'browsers',
559
609
  label: 'Browsers (solo)',
@@ -576,6 +626,12 @@ describe('createHeadlessForm', () => {
576
626
  });
577
627
 
578
628
  expect(fieldSelect).not.toHaveProperty('multiple');
629
+
630
+ assertOptionsAllowed({
631
+ handleValidation,
632
+ fieldName: 'browsers',
633
+ validOptions: ['chr', 'ff', 'ie'],
634
+ });
579
635
  });
580
636
 
581
637
  it('supports "select" field type with multiple options @deprecated', () => {
@@ -666,96 +722,157 @@ describe('createHeadlessForm', () => {
666
722
  });
667
723
 
668
724
  it('support "radio" field type @deprecated', () => {
669
- const result = createHeadlessForm(schemaInputTypeRadioDeprecated);
725
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
670
726
 
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
- });
727
+ expect(fields).toMatchObject([
728
+ {
729
+ description: 'Do you have any siblings?',
730
+ label: 'Has siblings',
731
+ name: 'has_siblings',
732
+ options: [
733
+ {
734
+ label: 'Yes',
735
+ value: 'yes',
736
+ },
737
+ {
738
+ label: 'No',
739
+ value: 'no',
740
+ },
741
+ ],
742
+ required: true,
743
+ schema: expect.any(Object),
744
+ type: 'radio',
745
+ },
746
+ ]);
693
747
 
694
- const fieldValidator = result.fields[0].schema;
695
- expect(fieldValidator.isValidSync('yes')).toBe(true);
696
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
748
+ assertOptionsAllowed({
749
+ handleValidation,
750
+ fieldName: 'has_siblings',
751
+ validOptions: ['yes', 'no'],
752
+ });
697
753
  });
698
754
  it('support "radio" field type', () => {
699
- const result = createHeadlessForm(schemaInputTypeRadio);
755
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
700
756
 
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
- });
757
+ expect(fields).toMatchObject([
758
+ {
759
+ description: 'Do you have any siblings?',
760
+ label: 'Has siblings',
761
+ name: 'has_siblings',
762
+ options: [
763
+ {
764
+ label: 'Yes',
765
+ value: 'yes',
766
+ },
767
+ {
768
+ label: 'No',
769
+ value: 'no',
770
+ },
771
+ ],
772
+ required: true,
773
+ schema: expect.any(Object),
774
+ type: 'radio',
775
+ },
776
+ ]);
723
777
 
724
- const fieldValidator = result.fields[0].schema;
725
- expect(fieldValidator.isValidSync('yes')).toBe(true);
726
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
778
+ assertOptionsAllowed({
779
+ handleValidation,
780
+ fieldName: 'has_siblings',
781
+ validOptions: ['yes', 'no'],
782
+ });
727
783
  });
728
784
 
729
785
  it('support "radio" optional field', () => {
730
- const result = createHeadlessForm(schemaInputTypeRadioRequiredAndOptional);
786
+ const { fields, handleValidation } = createHeadlessForm(
787
+ schemaInputTypeRadioRequiredAndOptional
788
+ );
789
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
731
790
 
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
- ],
791
+ expect(fields).toMatchObject([
792
+ {},
793
+ {
794
+ name: 'has_car',
795
+ label: 'Has car',
796
+ description: 'Do you have a car? (optional field, check oneOf)',
797
+ options: [
798
+ {
799
+ label: 'Yes',
800
+ value: 'yes',
801
+ },
802
+ {
803
+ label: 'No',
804
+ value: 'no',
805
+ },
806
+ ],
807
+ required: false,
808
+ schema: expect.any(Object),
809
+ type: 'radio',
810
+ },
811
+ ]);
812
+
813
+ expect(
814
+ validateForm({
815
+ has_siblings: 'yes',
816
+ has_car: 'yes',
817
+ })
818
+ ).toBeUndefined();
819
+ expect(validateForm({})).toEqual({
820
+ has_siblings: 'Required field',
754
821
  });
822
+ });
755
823
 
756
- const fieldValidator = result.fields[0].schema;
757
- expect(fieldValidator.isValidSync('yes')).toBe(true);
758
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
824
+ describe('support "radio" optional field - more examples @BUG RMT-518', () => {
825
+ function assertCommonBehavior(validateForm) {
826
+ // Note: Very similar to assertOptionsAllowed()
827
+ // We could reuse it in a next iteration.
828
+
829
+ // Happy path
830
+ expect(validateForm({ has_car: 'yes' })).toBeUndefined();
831
+
832
+ // Accepts undefined field
833
+ expect(validateForm({})).toBeUndefined();
834
+
835
+ // Does not accept other values
836
+ expect(validateForm({ has_car: 'blah-blah' })).toEqual({
837
+ has_car: 'The option "blah-blah" is not valid.',
838
+ });
839
+
840
+ // Does not accept "null" as string
841
+ expect(validateForm({ has_car: 'null' })).toEqual({
842
+ has_car: 'The option "null" is not valid.',
843
+ });
844
+
845
+ // Accepts empty string ("") — @BUG RMT-518
846
+ // Expectation: Does not accept empty string ("")
847
+ expect(validateForm({ has_car: '' })).toBeUndefined();
848
+ }
849
+
850
+ it('normal optional (conventional way)', () => {
851
+ const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
852
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
853
+
854
+ assertCommonBehavior(validateForm);
855
+
856
+ // Accepts null, even though it shoudln't @BUG RMT-518
857
+ // This is for cases where we (Remote) still have incorrect
858
+ // JSON Schemas in our Platform.
859
+ expect(validateForm({ has_car: null })).toBeUndefined();
860
+ // Expected:
861
+ // // Does NOT accept null value
862
+ // expect(validateForm({ has_car: null })).toEqual({
863
+ // has_car: 'The option null is not valid.',
864
+ // });
865
+ });
866
+
867
+ it('with null option (as Remote does)', () => {
868
+ const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
869
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
870
+
871
+ assertCommonBehavior(validateForm);
872
+
873
+ // Accepts null value
874
+ expect(validateForm({ has_car: null })).toBeUndefined();
875
+ });
759
876
  });
760
877
 
761
878
  it('support "radio" field type with extra info inside each option', () => {
@@ -1182,46 +1299,6 @@ describe('createHeadlessForm', () => {
1182
1299
  });
1183
1300
  });
1184
1301
 
1185
- it('supports "fieldset" field type', () => {
1186
- const result = createHeadlessForm(
1187
- JSONSchemaBuilder()
1188
- .addInput({
1189
- fieldset: mockFieldset,
1190
- })
1191
- .build()
1192
- );
1193
-
1194
- expect(result).toMatchObject({
1195
- fields: [
1196
- {
1197
- description: 'Fieldset description',
1198
- label: 'Fieldset title',
1199
- name: 'fieldset',
1200
- type: 'fieldset',
1201
- required: false,
1202
- fields: [
1203
- {
1204
- description: 'The number of your national identification (max 10 digits)',
1205
- label: 'ID number',
1206
- name: 'id_number',
1207
- type: 'text',
1208
- required: true,
1209
- },
1210
- {
1211
- description: 'How many open tabs do you have?',
1212
- label: 'Tabs',
1213
- maximum: 10,
1214
- minimum: 1,
1215
- name: 'tabs',
1216
- type: 'number',
1217
- required: false,
1218
- },
1219
- ],
1220
- },
1221
- ],
1222
- });
1223
- });
1224
-
1225
1302
  it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
1226
1303
  const result = createHeadlessForm(
1227
1304
  JSONSchemaBuilder()
@@ -1287,130 +1364,298 @@ describe('createHeadlessForm', () => {
1287
1364
  });
1288
1365
  });
1289
1366
 
1290
- 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
+ });
1404
1586
 
1405
- await expect(validateForm(formValues)).toBeUndefined();
1406
- expect(formValues.child.age).toBe(15);
1587
+ // The fieldset changed!
1588
+ const foodField = getField(fields, 'perks', 'food');
1589
+ // perks.food options changed ("No" was removed)
1590
+ expect(foodField.options).toHaveLength(3);
1591
+
1592
+ // Ensure the "no" option is no longer accepted:
1593
+ // This is a very important test in case the UI fails for some reason.
1594
+ expect(
1595
+ validateForm({
1596
+ work_hours_per_week: 35,
1597
+ pto: 20,
1598
+ perks: perksForLowWorkHours,
1599
+ })
1600
+ ).toEqual({
1601
+ perks: {
1602
+ food: 'The option "no" is not valid.',
1603
+ },
1604
+ });
1407
1605
 
1408
- formValues.child.has_child = 'no';
1409
- // form value updates re-validate; see computeYupSchema()
1410
- await expect(validateForm(formValues)).toBeUndefined();
1606
+ // perks.food has a new description
1607
+ expect(foodField.description).toBe("Above 30 hours, the 'no' option disappears.");
1608
+ // pto has a new description
1609
+ expect(getField(fields, 'pto').description).toBe(
1610
+ 'Above 30 hours, the PTO needs to be at least 20 days.'
1611
+ );
1411
1612
 
1412
- // when child.has_child is 'no' child.age is invisible
1413
- expect(formValues.child.age).toBe(null);
1613
+ // Sanity-check: Now the PTO also has a minimum value
1614
+ expect(
1615
+ validateForm({
1616
+ work_hours_per_week: 35,
1617
+ pto: 5, // too low
1618
+ perks: { food: 'lunch', retirement: 'basic' },
1619
+ })
1620
+ ).toEqual({
1621
+ pto: 'Must be greater or equal to 20',
1622
+ });
1623
+ });
1624
+
1625
+ it('When changing back to low work hours, the perks.food goes back to the original state', () => {
1626
+ expect(
1627
+ validateForm({
1628
+ work_hours_per_week: 10,
1629
+ pto: 5,
1630
+ })
1631
+ ).toEqual({
1632
+ perks: {
1633
+ food: 'Required field',
1634
+ retirement: 'Required field',
1635
+ },
1636
+ // ...pto is minimum error is gone! (sanity-check)
1637
+ });
1638
+
1639
+ const foodField = getField(fields, 'perks', 'food');
1640
+ // ...Number of perks.food options was back to the original (4)
1641
+ expect(foodField.options).toHaveLength(4);
1642
+ // ...Food description was back to the original
1643
+ expect(foodField.description).toBeUndefined();
1644
+
1645
+ // @BUG RMT-58 PTO description should disappear, but it didn't.
1646
+ expect(getField(fields, 'pto').description).toBe(
1647
+ 'Above 30 hours, the PTO needs to be at least 20 days.'
1648
+ );
1649
+
1650
+ // Given again "low perks", the form valid.
1651
+ expect(
1652
+ validateForm({
1653
+ work_hours_per_week: 10,
1654
+ perks: perksForLowWorkHours,
1655
+ })
1656
+ ).toBeUndefined();
1657
+ });
1658
+ });
1414
1659
  });
1415
1660
 
1416
1661
  it('support "email" field type', () => {
@@ -1729,14 +1974,12 @@ describe('createHeadlessForm', () => {
1729
1974
  strictInputType: false,
1730
1975
  });
1731
1976
 
1732
- const 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
  });