@remoteoss/json-schema-form 0.3.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,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,37 @@ 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(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
+
422
468
  it('support "text" field type', () => {
423
469
  const { fields, handleValidation } = createHeadlessForm(schemaInputTypeText);
424
470
 
@@ -521,38 +567,43 @@ describe('createHeadlessForm', () => {
521
567
  });
522
568
 
523
569
  it('support "select" field type @deprecated', () => {
524
- const result = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
570
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
525
571
 
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
- ],
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'],
550
600
  });
551
601
  });
602
+
552
603
  it('support "select" field type', () => {
553
- const result = createHeadlessForm(schemaInputTypeSelectSolo);
604
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
554
605
 
555
- const fieldSelect = result.fields[0];
606
+ const fieldSelect = fields[0];
556
607
  expect(fieldSelect).toMatchObject({
557
608
  name: 'browsers',
558
609
  label: 'Browsers (solo)',
@@ -575,6 +626,12 @@ describe('createHeadlessForm', () => {
575
626
  });
576
627
 
577
628
  expect(fieldSelect).not.toHaveProperty('multiple');
629
+
630
+ assertOptionsAllowed({
631
+ handleValidation,
632
+ fieldName: 'browsers',
633
+ validOptions: ['chr', 'ff', 'ie'],
634
+ });
578
635
  });
579
636
 
580
637
  it('supports "select" field type with multiple options @deprecated', () => {
@@ -665,96 +722,186 @@ describe('createHeadlessForm', () => {
665
722
  });
666
723
 
667
724
  it('support "radio" field type @deprecated', () => {
668
- const result = createHeadlessForm(schemaInputTypeRadioDeprecated);
725
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
669
726
 
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
- });
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
+ ]);
692
747
 
693
- const fieldValidator = result.fields[0].schema;
694
- expect(fieldValidator.isValidSync('yes')).toBe(true);
695
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
748
+ assertOptionsAllowed({
749
+ handleValidation,
750
+ fieldName: 'has_siblings',
751
+ validOptions: ['yes', 'no'],
752
+ });
696
753
  });
697
754
  it('support "radio" field type', () => {
698
- const result = createHeadlessForm(schemaInputTypeRadio);
755
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadio);
699
756
 
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
- });
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
+ ]);
722
777
 
723
- const fieldValidator = result.fields[0].schema;
724
- expect(fieldValidator.isValidSync('yes')).toBe(true);
725
- expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
778
+ assertOptionsAllowed({
779
+ handleValidation,
780
+ fieldName: 'has_siblings',
781
+ validOptions: ['yes', 'no'],
782
+ });
726
783
  });
727
784
 
728
785
  it('support "radio" optional field', () => {
729
- const result = createHeadlessForm(schemaInputTypeRadioRequiredAndOptional);
786
+ const { fields, handleValidation } = createHeadlessForm(
787
+ schemaInputTypeRadioRequiredAndOptional
788
+ );
789
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
730
790
 
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
- ],
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',
753
821
  });
822
+ });
754
823
 
755
- const fieldValidator = result.fields[0].schema;
756
- expect(fieldValidator.isValidSync('yes')).toBe(true);
757
- 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
+ });
876
+ });
877
+
878
+ it('support "radio" field type with extra info inside each option', () => {
879
+ const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
880
+
881
+ expect(result.fields).toHaveLength(1);
882
+
883
+ const fieldOptions = result.fields[0].options;
884
+
885
+ // The x-jsf-presentation content was spread to the root:
886
+ expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
887
+ expect(fieldOptions).toEqual([
888
+ {
889
+ label: 'Basic',
890
+ value: 'basic',
891
+ meta: {
892
+ displayCost: '$30.00/mo',
893
+ },
894
+ // Other x-* keywords are kept as it is.
895
+ 'x-another': 'extra-thing',
896
+ },
897
+ {
898
+ label: 'Standard',
899
+ value: 'standard',
900
+ meta: {
901
+ displayCost: '$50.00/mo',
902
+ },
903
+ },
904
+ ]);
758
905
  });
759
906
 
760
907
  it('support "number" field type', () => {
@@ -1152,46 +1299,6 @@ describe('createHeadlessForm', () => {
1152
1299
  });
1153
1300
  });
1154
1301
 
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
1302
  it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
1196
1303
  const result = createHeadlessForm(
1197
1304
  JSONSchemaBuilder()
@@ -1257,130 +1364,298 @@ describe('createHeadlessForm', () => {
1257
1364
  });
1258
1365
  });
1259
1366
 
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
- ],
1367
+ describe('supports "fieldset" field type', () => {
1368
+ it('supports basic case', () => {
1369
+ const result = createHeadlessForm({
1370
+ properties: {
1371
+ fieldset: mockFieldset,
1304
1372
  },
1305
- ],
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
+ });
1306
1404
  });
1307
- });
1308
1405
 
1309
- it('supported "fieldset" with scoped conditionals', () => {
1310
- const { handleValidation } = createHeadlessForm(schemaFieldsetScopedCondition, {});
1311
- 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
+ );
1312
1414
 
1313
- // The "child.has_child" is required
1314
- expect(validateForm({})).toEqual({
1315
- child: {
1316
- has_child: 'Required field',
1317
- },
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
+ });
1318
1453
  });
1319
1454
 
1320
- // The "child.no" is valid
1321
- expect(
1322
- 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({
1323
1461
  child: {
1324
- has_child: 'no',
1462
+ has_child: 'Required field',
1325
1463
  },
1326
- })
1327
- ).toBeUndefined();
1464
+ });
1328
1465
 
1329
- // Invalid because it expect child.age too
1330
- expect(
1331
- 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({
1332
1483
  child: {
1333
- has_child: 'yes',
1484
+ age: 'Required field',
1334
1485
  },
1335
- })
1336
- ).toEqual({
1337
- child: {
1338
- age: 'Required field',
1339
- },
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();
1340
1508
  });
1341
1509
 
1342
- // Valid without optional child.passport_id
1343
- expect(
1344
- validateForm({
1345
- child: {
1346
- has_child: 'yes',
1347
- age: 15,
1348
- },
1349
- })
1350
- ).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));
1351
1513
 
1352
- // Valid with optional child.passport_id
1353
- expect(
1354
- validateForm({
1514
+ const formValues = {
1355
1515
  child: {
1356
1516
  has_child: 'yes',
1357
1517
  age: 15,
1358
- passport_id: 'asdf',
1359
1518
  },
1360
- })
1361
- ).toBeUndefined();
1362
- });
1519
+ };
1363
1520
 
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));
1521
+ await expect(validateForm(formValues)).toBeUndefined();
1522
+ expect(formValues.child.age).toBe(15);
1367
1523
 
1368
- const formValues = {
1369
- child: {
1370
- has_child: 'yes',
1371
- age: 15,
1372
- },
1373
- };
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
+ });
1374
1638
 
1375
- await expect(validateForm(formValues)).toBeUndefined();
1376
- 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();
1377
1644
 
1378
- formValues.child.has_child = 'no';
1379
- // form value updates re-validate; see computeYupSchema()
1380
- 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
+ );
1381
1649
 
1382
- // when child.has_child is 'no' child.age is invisible
1383
- 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
+ });
1384
1659
  });
1385
1660
 
1386
1661
  it('support "email" field type', () => {
@@ -1699,14 +1974,12 @@ describe('createHeadlessForm', () => {
1699
1974
  strictInputType: false,
1700
1975
  });
1701
1976
 
1702
- const getByName = (fieldList, name) => fieldList.find((f) => f.name === name);
1703
-
1704
- const aFieldInRoot = getByName(fields, 'a_string');
1977
+ const aFieldInRoot = getField(fields, 'a_string');
1705
1978
  // It's the entire json schema
1706
1979
  expect(aFieldInRoot.scopedJsonSchema).toEqual(schemaWithoutInputTypes);
1707
1980
 
1708
- const aFieldset = getByName(fields, 'a_object');
1709
- const aFieldInTheFieldset = getByName(aFieldset.fields, 'foo');
1981
+ const aFieldset = getField(fields, 'a_object');
1982
+ const aFieldInTheFieldset = getField(aFieldset.fields, 'foo');
1710
1983
 
1711
1984
  // It's only the json schema of that fieldset
1712
1985
  expect(aFieldInTheFieldset.scopedJsonSchema).toEqual(
@@ -2166,8 +2439,6 @@ describe('createHeadlessForm', () => {
2166
2439
  });
2167
2440
 
2168
2441
  describe('when a JSON Schema is provided', () => {
2169
- const getByName = (fields, name) => fields.find((f) => f.name === name);
2170
-
2171
2442
  describe('and all fields are optional', () => {
2172
2443
  let handleValidation;
2173
2444
  const validateForm = (vals) => friendlyError(handleValidation(vals));
@@ -2574,23 +2845,23 @@ describe('createHeadlessForm', () => {
2574
2845
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2575
2846
  field_a: null,
2576
2847
  });
2577
- expect(getByName(fields, 'field_b').isVisible).toBe(false);
2848
+ expect(getField(fields, 'field_b').isVisible).toBe(false);
2578
2849
  });
2579
2850
 
2580
2851
  it('given a match, runs "then" (turns visible and editable)', () => {
2581
2852
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2582
2853
  initialValues: { field_a: 'yes' },
2583
2854
  });
2584
- expect(getByName(fields, 'field_b').isVisible).toBe(true);
2585
- 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);
2586
2857
  });
2587
2858
 
2588
2859
  it('given a nested match, runs "else-then" (turns visible but readOnly)', () => {
2589
2860
  const { fields } = createHeadlessForm(schemaWithConditionalReadOnlyProperty, {
2590
2861
  initialValues: { field_a: 'no' },
2591
2862
  });
2592
- expect(getByName(fields, 'field_b').isVisible).toBe(true);
2593
- 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);
2594
2865
  });
2595
2866
  });
2596
2867
 
@@ -2602,26 +2873,26 @@ describe('createHeadlessForm', () => {
2602
2873
  initialValues: { field_a: null, field_a_wrong: null },
2603
2874
  });
2604
2875
  // The dependent correct field gets hidden, but...
2605
- expect(getByName(fieldsEmpty, 'field_b').isVisible).toBe(false);
2876
+ expect(getField(fieldsEmpty, 'field_b').isVisible).toBe(false);
2606
2877
  // ...the dependent wrong field stays visible because the
2607
2878
  // conditional is wrong (it's missing the if.required[])
2608
- expect(getByName(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
2879
+ expect(getField(fieldsEmpty, 'field_b_wrong').isVisible).toBe(true);
2609
2880
  });
2610
2881
 
2611
2882
  it('given a match ("yes"), both runs "then" (turn visible)', () => {
2612
2883
  const { fields: fieldsVisible } = createHeadlessForm(schemaWithWrongConditional, {
2613
2884
  initialValues: { field_a: 'yes', field_a_wrong: 'yes' },
2614
2885
  });
2615
- expect(getByName(fieldsVisible, 'field_b').isVisible).toBe(true);
2616
- 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);
2617
2888
  });
2618
2889
 
2619
2890
  it('not given a match ("no"), both run else (stay hidden)', () => {
2620
2891
  const { fields: fieldsHidden } = createHeadlessForm(schemaWithWrongConditional, {
2621
2892
  initialValues: { field_a: 'no', field_a_wrong: 'no' },
2622
2893
  });
2623
- expect(getByName(fieldsHidden, 'field_b').isVisible).toBe(false);
2624
- 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);
2625
2896
  });
2626
2897
  });
2627
2898
 
@@ -2631,7 +2902,7 @@ describe('createHeadlessForm', () => {
2631
2902
  field_a: 'no',
2632
2903
  },
2633
2904
  });
2634
- const dependentField = getByName(fields, 'field_b');
2905
+ const dependentField = getField(fields, 'field_b');
2635
2906
  expect(dependentField.isVisible).toBe(false);
2636
2907
  expect(dependentField.value).toBe(undefined);
2637
2908
  });
@@ -2642,7 +2913,7 @@ describe('createHeadlessForm', () => {
2642
2913
  field_a: 'yes',
2643
2914
  },
2644
2915
  });
2645
- const dependentField = getByName(fields, 'field_b');
2916
+ const dependentField = getField(fields, 'field_b');
2646
2917
  expect(dependentField.isVisible).toBe(true);
2647
2918
  expect(dependentField.value).toBe(undefined);
2648
2919
  });