@remoteoss/json-schema-form 0.11.9-dev.20241210213029 → 0.11.10-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.
@@ -31,6 +31,7 @@ import {
31
31
  schemaInputWithStatement,
32
32
  schemaInputTypeCheckbox,
33
33
  schemaInputTypeCheckboxBooleans,
34
+ schemaInputTypeCheckboxBooleanConditional,
34
35
  schemaInputTypeNull,
35
36
  schemaWithOrderKeyword,
36
37
  schemaWithPositionDeprecated,
@@ -500,450 +501,450 @@ describe('createHeadlessForm', () => {
500
501
  expect(() => fieldValidator.validateSync('')).toThrowError('Required field');
501
502
  });
502
503
 
503
- it('supports both root level "description" and "x-jsf-presentation.description"', () => {
504
- const resultsWithRootDescription = createHeadlessForm(
505
- JSONSchemaBuilder()
506
- .addInput({
507
- username: mockTextInput,
508
- })
509
- .setRequiredFields(['username'])
510
- .build()
511
- );
512
-
513
- expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
504
+ describe('support "select" field type', () => {
505
+ it('support "select" field type @deprecated', () => {
506
+ const { fields, handleValidation } = createHeadlessForm(
507
+ schemaInputTypeSelectSoloDeprecated
508
+ );
514
509
 
515
- const resultsWithPresentationDescription = createHeadlessForm(
516
- JSONSchemaBuilder()
517
- .addInput({
518
- username: {
519
- ...mockTextInput,
520
- 'x-jsf-presentation': {
521
- inputType: 'text',
522
- maskSecret: 2,
523
- // should override the root level description
524
- description: 'a different description with <span>markup</span>',
510
+ expect(fields).toMatchObject([
511
+ {
512
+ description: 'Life Insurance',
513
+ label: 'Benefits (solo)',
514
+ name: 'benefits',
515
+ placeholder: 'Select...',
516
+ type: 'select',
517
+ options: [
518
+ {
519
+ label: 'Medical Insurance',
520
+ value: 'Medical Insurance',
525
521
  },
526
- },
527
- })
528
- .setRequiredFields(['username'])
529
- .build()
530
- );
531
-
532
- expect(resultsWithPresentationDescription.fields[0].description).toMatch(
533
- /a different description /i
534
- );
535
- });
536
-
537
- it('supports both root level "description" and "presentation.description" (deprecated)', () => {
538
- const resultsWithRootDescription = createHeadlessForm(
539
- JSONSchemaBuilder()
540
- .addInput({
541
- username: mockTextInputDeprecated,
542
- })
543
- .setRequiredFields(['username'])
544
- .build()
545
- );
546
-
547
- expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
548
-
549
- const resultsWithPresentationDescription = createHeadlessForm(
550
- JSONSchemaBuilder()
551
- .addInput({
552
- username: {
553
- ...mockTextInputDeprecated,
554
- presentation: {
555
- inputType: 'text',
556
- maskSecret: 2,
557
- // should override the root level description
558
- description: 'a different description with <span>markup</span>',
522
+ {
523
+ label: 'Health Insurance',
524
+ value: 'Health Insurance',
559
525
  },
560
- },
561
- })
562
- .setRequiredFields(['username'])
563
- .build()
564
- );
526
+ {
527
+ label: 'Travel Bonus',
528
+ value: 'Travel Bonus',
529
+ },
530
+ ],
531
+ },
532
+ ]);
565
533
 
566
- expect(resultsWithPresentationDescription.fields[0].description).toMatch(
567
- /a different description /i
568
- );
569
- });
534
+ assertOptionsAllowed({
535
+ handleValidation,
536
+ fieldName: 'benefits',
537
+ validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
538
+ });
539
+ });
570
540
 
571
- it('support "select" field type @deprecated', () => {
572
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSoloDeprecated);
541
+ it('support "select" field type', () => {
542
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
573
543
 
574
- expect(fields).toMatchObject([
575
- {
576
- description: 'Life Insurance',
577
- label: 'Benefits (solo)',
578
- name: 'benefits',
579
- placeholder: 'Select...',
580
- type: 'select',
544
+ const fieldSelect = fields[0];
545
+ expect(fieldSelect).toMatchObject({
546
+ name: 'browsers',
547
+ label: 'Browsers (solo)',
548
+ description: 'This solo select also includes a disabled option.',
581
549
  options: [
582
550
  {
583
- label: 'Medical Insurance',
584
- value: 'Medical Insurance',
551
+ value: 'chr',
552
+ label: 'Chrome',
585
553
  },
586
554
  {
587
- label: 'Health Insurance',
588
- value: 'Health Insurance',
555
+ value: 'ff',
556
+ label: 'Firefox',
589
557
  },
590
558
  {
591
- label: 'Travel Bonus',
592
- value: 'Travel Bonus',
559
+ value: 'ie',
560
+ label: 'Internet Explorer',
561
+ disabled: true,
593
562
  },
594
563
  ],
595
- },
596
- ]);
597
-
598
- assertOptionsAllowed({
599
- handleValidation,
600
- fieldName: 'benefits',
601
- validOptions: ['Medical Insurance', 'Health Insurance', 'Travel Bonus'],
602
- });
603
- });
564
+ });
604
565
 
605
- it('support "select" field type', () => {
606
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeSelectSolo);
566
+ expect(fieldSelect).not.toHaveProperty('multiple');
607
567
 
608
- const fieldSelect = fields[0];
609
- expect(fieldSelect).toMatchObject({
610
- name: 'browsers',
611
- label: 'Browsers (solo)',
612
- description: 'This solo select also includes a disabled option.',
613
- options: [
614
- {
615
- value: 'chr',
616
- label: 'Chrome',
617
- },
618
- {
619
- value: 'ff',
620
- label: 'Firefox',
621
- },
622
- {
623
- value: 'ie',
624
- label: 'Internet Explorer',
625
- disabled: true,
626
- },
627
- ],
568
+ assertOptionsAllowed({
569
+ handleValidation,
570
+ fieldName: 'browsers',
571
+ validOptions: ['chr', 'ff', 'ie'],
572
+ });
628
573
  });
629
574
 
630
- expect(fieldSelect).not.toHaveProperty('multiple');
575
+ it('supports "select" field type with multiple options @deprecated', () => {
576
+ const result = createHeadlessForm(schemaInputTypeSelectMultipleDeprecated);
577
+ expect(result).toMatchObject({
578
+ fields: [
579
+ {
580
+ description: 'Life Insurance',
581
+ label: 'Benefits (multiple)',
582
+ name: 'benefits_multi',
583
+ placeholder: 'Select...',
584
+ type: 'select',
585
+ options: [
586
+ {
587
+ label: 'Medical Insurance',
588
+ value: 'Medical Insurance',
589
+ },
590
+ {
591
+ label: 'Health Insurance',
592
+ value: 'Health Insurance',
593
+ },
594
+ {
595
+ label: 'Travel Bonus',
596
+ value: 'Travel Bonus',
597
+ },
598
+ ],
599
+ multiple: true,
600
+ },
601
+ ],
602
+ });
603
+ });
604
+ it('supports "select" field type with multiple options', () => {
605
+ const result = createHeadlessForm(schemaInputTypeSelectMultiple);
606
+ expect(result).toMatchObject({
607
+ fields: [
608
+ {
609
+ name: 'browsers_multi',
610
+ label: 'Browsers (multiple)',
611
+ description: 'This multi-select also includes a disabled option.',
612
+ options: [
613
+ {
614
+ value: 'chr',
615
+ label: 'Chrome',
616
+ },
617
+ {
618
+ value: 'ff',
619
+ label: 'Firefox',
620
+ },
621
+ {
622
+ value: 'ie',
623
+ label: 'Internet Explorer',
624
+ disabled: true,
625
+ },
626
+ ],
627
+ multiple: true,
628
+ },
629
+ ],
630
+ });
631
+ });
631
632
 
632
- assertOptionsAllowed({
633
- handleValidation,
634
- fieldName: 'browsers',
635
- validOptions: ['chr', 'ff', 'ie'],
633
+ it('supports "select" field type with multiple options and optional', () => {
634
+ const result = createHeadlessForm(schemaInputTypeSelectMultipleOptional);
635
+ expect(result).toMatchObject({
636
+ fields: [
637
+ {
638
+ name: 'browsers_multi_optional',
639
+ label: 'Browsers (multiple) (optional)',
640
+ description: 'This optional multi-select also includes a disabled option.',
641
+ options: [
642
+ {
643
+ value: 'chr',
644
+ label: 'Chrome',
645
+ },
646
+ {
647
+ value: 'ff',
648
+ label: 'Firefox',
649
+ },
650
+ {
651
+ value: 'ie',
652
+ label: 'Internet Explorer',
653
+ disabled: true,
654
+ },
655
+ ],
656
+ multiple: true,
657
+ },
658
+ ],
659
+ });
636
660
  });
637
661
  });
662
+ describe('support "radio" field type', () => {
663
+ it('support "radio" field type @deprecated', () => {
664
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
638
665
 
639
- it('supports "select" field type with multiple options @deprecated', () => {
640
- const result = createHeadlessForm(schemaInputTypeSelectMultipleDeprecated);
641
- expect(result).toMatchObject({
642
- fields: [
666
+ expect(fields).toMatchObject([
643
667
  {
644
- description: 'Life Insurance',
645
- label: 'Benefits (multiple)',
646
- name: 'benefits_multi',
647
- placeholder: 'Select...',
648
- type: 'select',
668
+ description: 'Do you have any siblings?',
669
+ label: 'Has siblings',
670
+ name: 'has_siblings',
649
671
  options: [
650
672
  {
651
- label: 'Medical Insurance',
652
- value: 'Medical Insurance',
673
+ label: 'Yes',
674
+ value: 'yes',
653
675
  },
654
676
  {
655
- label: 'Health Insurance',
656
- value: 'Health Insurance',
657
- },
658
- {
659
- label: 'Travel Bonus',
660
- value: 'Travel Bonus',
677
+ label: 'No',
678
+ value: 'no',
661
679
  },
662
680
  ],
663
- multiple: true,
681
+ required: true,
682
+ schema: expect.any(Object),
683
+ type: 'radio',
664
684
  },
665
- ],
685
+ ]);
686
+
687
+ assertOptionsAllowed({
688
+ handleValidation,
689
+ fieldName: 'has_siblings',
690
+ validOptions: ['yes', 'no'],
691
+ });
666
692
  });
667
- });
668
- it('supports "select" field type with multiple options', () => {
669
- const result = createHeadlessForm(schemaInputTypeSelectMultiple);
670
- expect(result).toMatchObject({
671
- fields: [
693
+ it('support "radio" field string type', () => {
694
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioString);
695
+
696
+ expect(fields).toMatchObject([
672
697
  {
673
- name: 'browsers_multi',
674
- label: 'Browsers (multiple)',
675
- description: 'This multi-select also includes a disabled option.',
698
+ description: 'Do you have any siblings?',
699
+ label: 'Has siblings',
700
+ name: 'has_siblings',
676
701
  options: [
677
702
  {
678
- value: 'chr',
679
- label: 'Chrome',
680
- },
681
- {
682
- value: 'ff',
683
- label: 'Firefox',
703
+ label: 'Yes',
704
+ value: 'yes',
684
705
  },
685
706
  {
686
- value: 'ie',
687
- label: 'Internet Explorer',
688
- disabled: true,
707
+ label: 'No',
708
+ value: 'no',
689
709
  },
690
710
  ],
691
- multiple: true,
711
+ required: true,
712
+ schema: expect.any(Object),
713
+ type: 'radio',
692
714
  },
693
- ],
715
+ ]);
716
+
717
+ assertOptionsAllowed({
718
+ handleValidation,
719
+ fieldName: 'has_siblings',
720
+ validOptions: ['yes', 'no'],
721
+ });
694
722
  });
695
- });
696
723
 
697
- it('supports "select" field type with multiple options and optional', () => {
698
- const result = createHeadlessForm(schemaInputTypeSelectMultipleOptional);
699
- expect(result).toMatchObject({
700
- fields: [
724
+ it('support "radio" field boolean type', () => {
725
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioBoolean);
726
+
727
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
728
+
729
+ expect(fields).toMatchObject([
701
730
  {
702
- name: 'browsers_multi_optional',
703
- label: 'Browsers (multiple) (optional)',
704
- description: 'This optional multi-select also includes a disabled option.',
731
+ description: 'Are you over 18 years old?',
732
+ label: 'Over 18',
733
+ name: 'over_18',
705
734
  options: [
706
735
  {
707
- value: 'chr',
708
- label: 'Chrome',
709
- },
710
- {
711
- value: 'ff',
712
- label: 'Firefox',
736
+ label: 'Yes',
737
+ value: true,
713
738
  },
714
739
  {
715
- value: 'ie',
716
- label: 'Internet Explorer',
717
- disabled: true,
740
+ label: 'No',
741
+ value: false,
718
742
  },
719
743
  ],
720
- multiple: true,
744
+ required: true,
745
+ schema: expect.any(Object),
746
+ type: 'radio',
721
747
  },
722
- ],
723
- });
724
- });
725
-
726
- it('support "radio" field type @deprecated', () => {
727
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioDeprecated);
728
-
729
- expect(fields).toMatchObject([
730
- {
731
- description: 'Do you have any siblings?',
732
- label: 'Has siblings',
733
- name: 'has_siblings',
734
- options: [
735
- {
736
- label: 'Yes',
737
- value: 'yes',
738
- },
739
- {
740
- label: 'No',
741
- value: 'no',
742
- },
743
- ],
744
- required: true,
745
- schema: expect.any(Object),
746
- type: 'radio',
747
- },
748
- ]);
749
-
750
- assertOptionsAllowed({
751
- handleValidation,
752
- fieldName: 'has_siblings',
753
- validOptions: ['yes', 'no'],
754
- });
755
- });
756
- it('support "radio" field string type', () => {
757
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioString);
748
+ ]);
758
749
 
759
- expect(fields).toMatchObject([
760
- {
761
- description: 'Do you have any siblings?',
762
- label: 'Has siblings',
763
- name: 'has_siblings',
764
- options: [
765
- {
766
- label: 'Yes',
767
- value: 'yes',
768
- },
769
- {
770
- label: 'No',
771
- value: 'no',
772
- },
773
- ],
774
- required: true,
775
- schema: expect.any(Object),
776
- type: 'radio',
777
- },
778
- ]);
750
+ assertOptionsAllowed({
751
+ handleValidation,
752
+ fieldName: 'over_18',
753
+ validOptions: [true, false],
754
+ type: schemaInputTypeRadioBoolean.properties.over_18.type,
755
+ });
779
756
 
780
- assertOptionsAllowed({
781
- handleValidation,
782
- fieldName: 'has_siblings',
783
- validOptions: ['yes', 'no'],
757
+ expect(validateForm({ over_18: 'true' })).toEqual({
758
+ over_18: 'The option "true" is not valid.',
759
+ });
784
760
  });
785
- });
786
761
 
787
- it('support "radio" field boolean type', () => {
788
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioBoolean);
789
-
790
- const validateForm = (vals) => friendlyError(handleValidation(vals));
762
+ it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
763
+ const result = createHeadlessForm(
764
+ JSONSchemaBuilder()
765
+ .addInput({
766
+ experience_level: mockRadioCardExpandableInput,
767
+ payment_method: mockRadioCardInput,
768
+ })
769
+ .build()
770
+ );
791
771
 
792
- expect(fields).toMatchObject([
793
- {
794
- description: 'Are you over 18 years old?',
795
- label: 'Over 18',
796
- name: 'over_18',
797
- options: [
772
+ expect(result).toMatchObject({
773
+ fields: [
798
774
  {
799
- label: 'Yes',
800
- value: true,
775
+ description:
776
+ 'Please select the experience level that aligns with this role based on the job description (not the employees overall experience)',
777
+ label: 'Experience level',
778
+ name: 'experience_level',
779
+ type: 'radio',
780
+ required: false,
781
+ variant: 'card-expandable',
782
+ options: [
783
+ {
784
+ label: 'Junior level',
785
+ value: 'junior',
786
+ description:
787
+ 'Entry level employees who perform tasks under the supervision of a more experienced employee.',
788
+ },
789
+ {
790
+ label: 'Mid level',
791
+ value: 'mid',
792
+ description:
793
+ 'Employees who perform tasks with a good degree of autonomy and/or with coordination and control functions.',
794
+ },
795
+ {
796
+ label: 'Senior level',
797
+ value: 'senior',
798
+ description:
799
+ 'Employees who perform tasks with a high degree of autonomy and/or with coordination and control functions.',
800
+ },
801
+ ],
801
802
  },
802
803
  {
803
- label: 'No',
804
- value: false,
804
+ description: 'Chose how you want to be paid',
805
+ label: 'Payment method',
806
+ name: 'payment_method',
807
+ type: 'radio',
808
+ variant: 'card',
809
+ required: false,
810
+ options: [
811
+ {
812
+ label: 'Credit Card',
813
+ value: 'cc',
814
+ description: 'Plastic money, which is still money',
815
+ },
816
+ {
817
+ label: 'Cash',
818
+ value: 'cash',
819
+ description: 'Rules Everything Around Me',
820
+ },
821
+ ],
805
822
  },
806
823
  ],
807
- required: true,
808
- schema: expect.any(Object),
809
- type: 'radio',
810
- },
811
- ]);
812
-
813
- assertOptionsAllowed({
814
- handleValidation,
815
- fieldName: 'over_18',
816
- validOptions: [true, false],
817
- type: schemaInputTypeRadioBoolean.properties.over_18.type,
824
+ });
818
825
  });
819
826
 
820
- expect(validateForm({ over_18: 'true' })).toEqual({
821
- over_18: 'The option "true" is not valid.',
822
- });
823
- });
827
+ // @BUG COD-1859
828
+ // it should validate when type is string but value is not a boolean
829
+ it('support "radio" field string-y type', () => {
830
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioStringY);
824
831
 
825
- // @BUG COD-1859
826
- // it should validate when type is string but value is not a boolean
827
- it('support "radio" field string-y type', () => {
828
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioStringY);
829
-
830
- const validateForm = (vals) => friendlyError(handleValidation(vals));
832
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
831
833
 
832
- expect(fields).toMatchObject([
833
- {
834
- description: 'Do you have any siblings?',
835
- label: 'Has siblings',
836
- name: 'has_siblings',
837
- options: [
838
- {
839
- label: 'Yes',
840
- value: 'true',
841
- },
842
- {
843
- label: 'No',
844
- value: 'false',
845
- },
846
- ],
847
- required: true,
848
- schema: expect.any(Object),
849
- type: 'radio',
850
- },
851
- ]);
834
+ expect(fields).toMatchObject([
835
+ {
836
+ description: 'Do you have any siblings?',
837
+ label: 'Has siblings',
838
+ name: 'has_siblings',
839
+ options: [
840
+ {
841
+ label: 'Yes',
842
+ value: 'true',
843
+ },
844
+ {
845
+ label: 'No',
846
+ value: 'false',
847
+ },
848
+ ],
849
+ required: true,
850
+ schema: expect.any(Object),
851
+ type: 'radio',
852
+ },
853
+ ]);
852
854
 
853
- assertOptionsAllowed({
854
- handleValidation,
855
- fieldName: 'has_siblings',
856
- validOptions: ['true', 'false'],
857
- });
855
+ assertOptionsAllowed({
856
+ handleValidation,
857
+ fieldName: 'has_siblings',
858
+ validOptions: ['true', 'false'],
859
+ });
858
860
 
859
- expect(validateForm({ has_siblings: false })).toEqual({
860
- has_siblings: 'The option "false" is not valid.',
861
+ expect(validateForm({ has_siblings: false })).toEqual({
862
+ has_siblings: 'The option "false" is not valid.',
863
+ });
861
864
  });
862
- });
863
865
 
864
- it('support "radio" field number type', () => {
865
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioNumber);
866
+ it('support "radio" field number type', () => {
867
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeRadioNumber);
866
868
 
867
- const validateForm = (vals) => friendlyError(handleValidation(vals));
869
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
868
870
 
869
- expect(fields).toMatchObject([
870
- {
871
- description: 'How many siblings do you have?',
872
- label: 'Number of siblings',
873
- name: 'siblings_count',
874
- options: [
875
- {
876
- label: 'One',
877
- value: 1,
878
- },
879
- {
880
- label: 'Two',
881
- value: 2,
882
- },
883
- {
884
- label: 'Three',
885
- value: 3,
886
- },
887
- ],
888
- required: true,
889
- schema: expect.any(Object),
890
- type: 'radio',
891
- },
892
- ]);
871
+ expect(fields).toMatchObject([
872
+ {
873
+ description: 'How many siblings do you have?',
874
+ label: 'Number of siblings',
875
+ name: 'siblings_count',
876
+ options: [
877
+ {
878
+ label: 'One',
879
+ value: 1,
880
+ },
881
+ {
882
+ label: 'Two',
883
+ value: 2,
884
+ },
885
+ {
886
+ label: 'Three',
887
+ value: 3,
888
+ },
889
+ ],
890
+ required: true,
891
+ schema: expect.any(Object),
892
+ type: 'radio',
893
+ },
894
+ ]);
893
895
 
894
- assertOptionsAllowed({
895
- handleValidation,
896
- fieldName: 'siblings_count',
897
- validOptions: [1, 2, 3],
898
- type: schemaInputTypeRadioNumber.properties.siblings_count.type,
899
- });
896
+ assertOptionsAllowed({
897
+ handleValidation,
898
+ fieldName: 'siblings_count',
899
+ validOptions: [1, 2, 3],
900
+ type: schemaInputTypeRadioNumber.properties.siblings_count.type,
901
+ });
900
902
 
901
- expect(validateForm({ siblings_count: '3' })).toEqual({
902
- siblings_count: 'The option "3" is not valid.',
903
+ expect(validateForm({ siblings_count: '3' })).toEqual({
904
+ siblings_count: 'The option "3" is not valid.',
905
+ });
903
906
  });
904
- });
905
907
 
906
- it('support "radio" optional field', () => {
907
- const { fields, handleValidation } = createHeadlessForm(
908
- schemaInputTypeRadioRequiredAndOptional
909
- );
910
- const validateForm = (vals) => friendlyError(handleValidation(vals));
908
+ it('support "radio" optional field', () => {
909
+ const { fields, handleValidation } = createHeadlessForm(
910
+ schemaInputTypeRadioRequiredAndOptional
911
+ );
912
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
911
913
 
912
- expect(fields).toMatchObject([
913
- {},
914
- {
915
- name: 'has_car',
916
- label: 'Has car',
917
- description: 'Do you have a car? (optional field, check oneOf)',
918
- options: [
919
- {
920
- label: 'Yes',
921
- value: 'yes',
922
- },
923
- {
924
- label: 'No',
925
- value: 'no',
926
- },
927
- ],
928
- required: false,
929
- schema: expect.any(Object),
930
- type: 'radio',
931
- },
932
- ]);
914
+ expect(fields).toMatchObject([
915
+ {},
916
+ {
917
+ name: 'has_car',
918
+ label: 'Has car',
919
+ description: 'Do you have a car? (optional field, check oneOf)',
920
+ options: [
921
+ {
922
+ label: 'Yes',
923
+ value: 'yes',
924
+ },
925
+ {
926
+ label: 'No',
927
+ value: 'no',
928
+ },
929
+ ],
930
+ required: false,
931
+ schema: expect.any(Object),
932
+ type: 'radio',
933
+ },
934
+ ]);
933
935
 
934
- expect(
935
- validateForm({
936
- has_siblings: 'yes',
937
- has_car: 'yes',
938
- })
939
- ).toBeUndefined();
936
+ expect(
937
+ validateForm({
938
+ has_siblings: 'yes',
939
+ has_car: 'yes',
940
+ })
941
+ ).toBeUndefined();
940
942
 
941
- expect(validateForm({})).toEqual({
942
- has_siblings: 'Required field',
943
+ expect(validateForm({})).toEqual({
944
+ has_siblings: 'Required field',
945
+ });
943
946
  });
944
- });
945
947
 
946
- describe('support "radio" optional field - more examples @BUG RMT-518', () => {
947
948
  function assertCommonBehavior(validateForm) {
948
949
  // Note: Very similar to assertOptionsAllowed()
949
950
  // We could reuse it in a next iteration.
@@ -969,13 +970,13 @@ describe('createHeadlessForm', () => {
969
970
  expect(validateForm({ has_car: '' })).toBeUndefined();
970
971
  }
971
972
 
972
- it('normal optional (conventional way)', () => {
973
+ it('support "radio" optional field - optional (conventional way) - @BUG RMT-518', () => {
973
974
  const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalConventional);
974
975
  const validateForm = (vals) => friendlyError(handleValidation(vals));
975
976
 
976
977
  assertCommonBehavior(validateForm);
977
978
 
978
- // Accepts null, even though it shoudln't @BUG RMT-518
979
+ // Accepts null, even though it shouldn't @BUG RMT-518
979
980
  // This is for cases where we (Remote) still have incorrect
980
981
  // JSON Schemas in our Platform.
981
982
  expect(validateForm({ has_car: null })).toBeUndefined();
@@ -986,53 +987,98 @@ describe('createHeadlessForm', () => {
986
987
  // });
987
988
  });
988
989
 
989
- it('with null option (as Remote does)', () => {
990
+ it('support "radio" optional field - optional with null option (as Remote does) - @BUG RMT-518', () => {
990
991
  const { handleValidation } = createHeadlessForm(schemaInputRadioOptionalNull);
991
992
  const validateForm = (vals) => friendlyError(handleValidation(vals));
992
993
 
993
- assertCommonBehavior(validateForm);
994
+ assertCommonBehavior(validateForm);
995
+
996
+ // Accepts null value
997
+ expect(validateForm({ has_car: null })).toBeUndefined();
998
+ });
999
+
1000
+ it('support "radio" field type with extra info inside each option', () => {
1001
+ const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
1002
+
1003
+ expect(result.fields).toHaveLength(1);
1004
+
1005
+ const fieldOptions = result.fields[0].options;
1006
+
1007
+ // The x-jsf-presentation content was spread to the root:
1008
+ expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
1009
+ expect(fieldOptions).toEqual([
1010
+ {
1011
+ label: 'Basic',
1012
+ value: 'basic',
1013
+ meta: {
1014
+ displayCost: '$30.00/mo',
1015
+ },
1016
+ // Other x-* keywords are kept as it is.
1017
+ 'x-another': 'extra-thing',
1018
+ },
1019
+ {
1020
+ label: 'Standard',
1021
+ value: 'standard',
1022
+ meta: {
1023
+ displayCost: '$50.00/mo',
1024
+ },
1025
+ },
1026
+ ]);
1027
+ });
1028
+
1029
+ it('supports oneOf pattern validation', () => {
1030
+ const result = createHeadlessForm(mockTelWithPattern);
1031
+
1032
+ expect(result).toMatchObject({
1033
+ fields: [
1034
+ {
1035
+ label: 'Phone number',
1036
+ name: 'phone_number',
1037
+ type: 'tel',
1038
+ required: false,
1039
+ options: [
1040
+ {
1041
+ label: 'Portugal',
1042
+ pattern: '^(\\+351)[0-9]{9,}$',
1043
+ },
1044
+ {
1045
+ label: 'United Kingdom (UK)',
1046
+ pattern: '^(\\+44)[0-9]{1,}$',
1047
+ },
1048
+ {
1049
+ label: 'Bolivia',
1050
+ pattern: '^(\\+591)[0-9]{9,}$',
1051
+ },
1052
+ {
1053
+ label: 'Canada',
1054
+ pattern: '^(\\+1)(206|224)[0-9]{1,}$',
1055
+ },
1056
+ {
1057
+ label: 'United States',
1058
+ pattern: '^(\\+1)[0-9]{1,}$',
1059
+ },
1060
+ ],
1061
+ },
1062
+ ],
1063
+ });
1064
+
1065
+ const fieldValidator = result.fields[0].schema;
994
1066
 
995
- // Accepts null value
996
- expect(validateForm({ has_car: null })).toBeUndefined();
1067
+ expect(fieldValidator.isValidSync('+351123123123')).toBe(true);
1068
+ expect(() => fieldValidator.validateSync('+35100')).toThrowError(
1069
+ 'The option "+35100" is not valid.'
1070
+ );
1071
+ expect(fieldValidator.isValidSync(undefined)).toBe(true);
997
1072
  });
998
- });
999
-
1000
- it('support "radio" field type with extra info inside each option', () => {
1001
- const result = createHeadlessForm(schemaInputTypeRadioOptionsWithDetails);
1002
-
1003
- expect(result.fields).toHaveLength(1);
1004
-
1005
- const fieldOptions = result.fields[0].options;
1006
-
1007
- // The x-jsf-presentation content was spread to the root:
1008
- expect(fieldOptions[0]).not.toHaveProperty('x-jsf-presentation');
1009
- expect(fieldOptions).toEqual([
1010
- {
1011
- label: 'Basic',
1012
- value: 'basic',
1013
- meta: {
1014
- displayCost: '$30.00/mo',
1015
- },
1016
- // Other x-* keywords are kept as it is.
1017
- 'x-another': 'extra-thing',
1018
- },
1019
- {
1020
- label: 'Standard',
1021
- value: 'standard',
1022
- meta: {
1023
- displayCost: '$50.00/mo',
1024
- },
1025
- },
1026
- ]);
1027
- });
1028
1073
 
1029
- it('support "radio" field type without oneOf options', () => {
1030
- const result = createHeadlessForm(schemaInputTypeRadioWithoutOptions);
1074
+ it('support "radio" field type without oneOf options', () => {
1075
+ const result = createHeadlessForm(schemaInputTypeRadioWithoutOptions);
1031
1076
 
1032
- expect(result.fields).toHaveLength(1);
1077
+ expect(result.fields).toHaveLength(1);
1033
1078
 
1034
- const fieldOptions = result.fields[0].options;
1035
- expect(fieldOptions).toEqual([]);
1079
+ const fieldOptions = result.fields[0].options;
1080
+ expect(fieldOptions).toEqual([]);
1081
+ });
1036
1082
  });
1037
1083
 
1038
1084
  it('support "integer" field type', () => {
@@ -1201,110 +1247,298 @@ describe('createHeadlessForm', () => {
1201
1247
  });
1202
1248
  });
1203
1249
 
1204
- it('support "date" field type with a minDate', () => {
1205
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
1250
+ describe('support "date" field type', () => {
1251
+ it('support "date" field type with a minDate', () => {
1252
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
1206
1253
 
1207
- const validateForm = (vals) => friendlyError(handleValidation(vals));
1254
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
1208
1255
 
1209
- expect(fields[0]).toMatchObject({
1210
- label: 'Birthdate',
1211
- name: 'birthdate',
1212
- required: true,
1213
- schema: expect.any(Object),
1214
- type: 'date',
1215
- minDate: '1922-03-01',
1216
- maxDate: '2022-03-17',
1256
+ expect(fields[0]).toMatchObject({
1257
+ label: 'Birthdate',
1258
+ name: 'birthdate',
1259
+ required: true,
1260
+ schema: expect.any(Object),
1261
+ type: 'date',
1262
+ minDate: '1922-03-01',
1263
+ maxDate: '2022-03-17',
1264
+ });
1265
+
1266
+ expect(validateForm({})).toEqual({
1267
+ birthdate: 'Required field',
1268
+ });
1269
+
1270
+ expect(validateForm({ birthdate: '' })).toEqual({
1271
+ birthdate: `Required field`,
1272
+ });
1273
+
1274
+ expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
1275
+ birthdate: 'The date must be 1922-03-01 or after.',
1276
+ });
1277
+
1278
+ expect(validateForm({ birthdate: '1922-03-01' })).toBeUndefined();
1279
+
1280
+ expect(validateForm({ birthdate: '2021-03-01' })).toBeUndefined();
1217
1281
  });
1218
1282
 
1219
- expect(validateForm({})).toEqual({
1220
- birthdate: 'Required field',
1283
+ it('support "date" field type with a maxDate', () => {
1284
+ const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
1285
+
1286
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
1287
+
1288
+ expect(fields[0]).toMatchObject({
1289
+ label: 'Birthdate',
1290
+ name: 'birthdate',
1291
+ required: true,
1292
+ schema: expect.any(Object),
1293
+ type: 'date',
1294
+ minDate: '1922-03-01',
1295
+ maxDate: '2022-03-17',
1296
+ });
1297
+
1298
+ expect(validateForm({ birthdate: '' })).toEqual({
1299
+ birthdate: `Required field`,
1300
+ });
1301
+
1302
+ expect(validateForm({ birthdate: '2022-02-01' })).toBeUndefined();
1303
+ expect(validateForm({ birthdate: '2022-03-01' })).toBeUndefined();
1304
+ expect(validateForm({ birthdate: '2022-04-01' })).toEqual({
1305
+ birthdate: 'The date must be 2022-03-17 or before.',
1306
+ });
1221
1307
  });
1222
1308
 
1223
- expect(validateForm({ birthdate: '' })).toEqual({
1224
- birthdate: `Required field`,
1309
+ it('support format date with minDate and maxDate', () => {
1310
+ const schemaFormatDate = {
1311
+ properties: {
1312
+ birthdate: {
1313
+ title: 'Birthdate',
1314
+ type: 'string',
1315
+ format: 'date',
1316
+ 'x-jsf-presentation': {
1317
+ inputType: 'myDateType',
1318
+ maxDate: '2022-03-01',
1319
+ minDate: '1922-03-01',
1320
+ },
1321
+ },
1322
+ },
1323
+ };
1324
+
1325
+ const { handleValidation } = createHeadlessForm(schemaFormatDate);
1326
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
1327
+
1328
+ expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
1329
+ birthdate: 'The date must be 1922-03-01 or after.',
1330
+ });
1225
1331
  });
1332
+ });
1333
+
1334
+ describe('supports "file" field type', () => {
1335
+ it('supports "file" field type', () => {
1336
+ const result = createHeadlessForm(
1337
+ JSONSchemaBuilder()
1338
+ .addInput({
1339
+ fileInput: mockFileInput,
1340
+ })
1341
+ .build()
1342
+ );
1226
1343
 
1227
- expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
1228
- birthdate: 'The date must be 1922-03-01 or after.',
1344
+ expect(result).toMatchObject({
1345
+ fields: [
1346
+ {
1347
+ type: 'file',
1348
+ fileDownload: 'http://some.domain.com/file-name.pdf',
1349
+ description: 'File Input Description',
1350
+ fileName: 'My File',
1351
+ label: 'File Input',
1352
+ name: 'fileInput',
1353
+ required: false,
1354
+ accept: '.png,.jpg,.jpeg,.pdf',
1355
+ },
1356
+ ],
1357
+ });
1229
1358
  });
1230
1359
 
1231
- expect(validateForm({ birthdate: '1922-03-01' })).toBeUndefined();
1360
+ describe('when a field has accepted extensions', () => {
1361
+ let fields;
1362
+ beforeEach(() => {
1363
+ const result = createHeadlessForm(
1364
+ JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
1365
+ );
1366
+ fields = result.fields;
1367
+ });
1232
1368
 
1233
- expect(validateForm({ birthdate: '2021-03-01' })).toBeUndefined();
1234
- });
1369
+ describe('and file is of incorrect format', () => {
1370
+ const file = new File(['foo'], 'file.txt', {
1371
+ type: 'text/plain',
1372
+ });
1235
1373
 
1236
- it('support "date" field type with a maxDate', () => {
1237
- const { fields, handleValidation } = createHeadlessForm(schemaInputTypeDate);
1374
+ it('should throw an error', async () =>
1375
+ expect(
1376
+ object()
1377
+ .shape({
1378
+ fileInput: fields[0].schema,
1379
+ })
1380
+ .validate({ fileInput: [file] })
1381
+ ).rejects.toMatchObject({
1382
+ errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
1383
+ }));
1384
+ });
1238
1385
 
1239
- const validateForm = (vals) => friendlyError(handleValidation(vals));
1386
+ describe('and file is of correct format', () => {
1387
+ const file = new File(['foo'], 'file.png', {
1388
+ type: 'image/png',
1389
+ });
1390
+ Object.defineProperty(file, 'size', { value: 1024 * 1024 });
1391
+
1392
+ const assertObj = { fileInput: [file] };
1393
+ it('should validate field', async () =>
1394
+ expect(
1395
+ object()
1396
+ .shape({
1397
+ fileInput: fields[0].schema,
1398
+ })
1399
+ .validate({ fileInput: [file] })
1400
+ ).resolves.toEqual(assertObj));
1401
+ });
1240
1402
 
1241
- expect(fields[0]).toMatchObject({
1242
- label: 'Birthdate',
1243
- name: 'birthdate',
1244
- required: true,
1245
- schema: expect.any(Object),
1246
- type: 'date',
1247
- minDate: '1922-03-01',
1248
- maxDate: '2022-03-17',
1249
- });
1403
+ describe('and file is of correct but uppercase format ', () => {
1404
+ const file = new File(['foo'], 'file.PNG', {
1405
+ type: 'image/png',
1406
+ });
1407
+ Object.defineProperty(file, 'size', { value: 1024 * 1024 });
1408
+
1409
+ const assertObj = { fileInput: [file] };
1410
+ it('should validate field', async () =>
1411
+ expect(
1412
+ object()
1413
+ .shape({
1414
+ fileInput: fields[0].schema,
1415
+ })
1416
+ .validate({ fileInput: [file] })
1417
+ ).resolves.toEqual(assertObj));
1418
+ });
1419
+
1420
+ describe('and file is not instance of a File', () => {
1421
+ it('accepts if file object has name property', async () => {
1422
+ expect(
1423
+ object()
1424
+ .shape({
1425
+ fileInput: fields[0].schema,
1426
+ })
1427
+ .validate({ fileInput: [{ name: 'foo.pdf' }] })
1428
+ ).resolves.toEqual({ fileInput: [{ name: 'foo.pdf' }] });
1429
+ });
1250
1430
 
1251
- expect(validateForm({ birthdate: '' })).toEqual({
1252
- birthdate: `Required field`,
1431
+ it('should validate format', async () =>
1432
+ expect(
1433
+ object()
1434
+ .shape({
1435
+ fileInput: fields[0].schema,
1436
+ })
1437
+ .validate({ fileInput: [{ name: 'foo.txt' }] })
1438
+ ).rejects.toMatchObject({
1439
+ errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
1440
+ }));
1441
+
1442
+ it('should validate max size', async () =>
1443
+ expect(
1444
+ object()
1445
+ .shape({
1446
+ fileInput: fields[0].schema,
1447
+ })
1448
+ .validate({ fileInput: [{ name: 'foo.txt', size: 1024 * 1024 * 1024 }] })
1449
+ ).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
1450
+
1451
+ it('throw an error if invalid file object', async () =>
1452
+ expect(
1453
+ object()
1454
+ .shape({
1455
+ fileInput: fields[0].schema,
1456
+ })
1457
+ .validate({ fileInput: [{ path: 'foo.txt' }] })
1458
+ ).rejects.toMatchObject({ errors: ['Not a valid file.'] }));
1459
+ });
1253
1460
  });
1254
1461
 
1255
- expect(validateForm({ birthdate: '2022-02-01' })).toBeUndefined();
1256
- expect(validateForm({ birthdate: '2022-03-01' })).toBeUndefined();
1257
- expect(validateForm({ birthdate: '2022-04-01' })).toEqual({
1258
- birthdate: 'The date must be 2022-03-17 or before.',
1462
+ describe('when a field has max file size', () => {
1463
+ let fields;
1464
+ beforeEach(() => {
1465
+ const result = createHeadlessForm(
1466
+ JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
1467
+ );
1468
+ fields = result.fields;
1469
+ });
1470
+ describe('and file is greater than that', () => {
1471
+ const file = new File([''], 'file.png');
1472
+ Object.defineProperty(file, 'size', { value: 1024 * 1024 * 1024 });
1473
+
1474
+ it('should throw an error', async () =>
1475
+ expect(
1476
+ object()
1477
+ .shape({
1478
+ fileInput: fields[0].schema,
1479
+ })
1480
+ .validate({ fileInput: [file] })
1481
+ ).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
1482
+ });
1483
+ describe('and file is smaller than that', () => {
1484
+ const file = new File([''], 'file.png');
1485
+ Object.defineProperty(file, 'size', { value: 1024 * 1024 });
1486
+
1487
+ const assertObj = { fileInput: [file] };
1488
+ it('should validate field', async () =>
1489
+ expect(
1490
+ object()
1491
+ .shape({
1492
+ fileInput: fields[0].schema,
1493
+ })
1494
+ .validate({ fileInput: [file] })
1495
+ ).resolves.toEqual(assertObj));
1496
+ });
1259
1497
  });
1260
- });
1261
1498
 
1262
- it('support format date with minDate and maxDate', () => {
1263
- const schemaFormatDate = {
1264
- properties: {
1265
- birthdate: {
1266
- title: 'Birthdate',
1267
- type: 'string',
1268
- format: 'date',
1269
- 'x-jsf-presentation': {
1270
- inputType: 'myDateType',
1271
- maxDate: '2022-03-01',
1272
- minDate: '1922-03-01',
1273
- },
1274
- },
1275
- },
1276
- };
1499
+ describe('when a field file is optional', () => {
1500
+ it('it accepts an empty array', () => {
1501
+ const result = createHeadlessForm(
1502
+ JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
1503
+ );
1504
+ const emptyFile = { fileInput: [] };
1505
+ expect(
1506
+ object()
1507
+ .shape({
1508
+ fileInput: result.fields[0].schema,
1509
+ })
1510
+ .validate(emptyFile)
1511
+ ).resolves.toEqual(emptyFile);
1512
+ });
1277
1513
 
1278
- const { handleValidation } = createHeadlessForm(schemaFormatDate);
1279
- const validateForm = (vals) => friendlyError(handleValidation(vals));
1514
+ it('it validates missing file correctly', () => {
1515
+ const { handleValidation } = createHeadlessForm(
1516
+ JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
1517
+ );
1518
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
1280
1519
 
1281
- expect(validateForm({ birthdate: '1922-02-01' })).toEqual({
1282
- birthdate: 'The date must be 1922-03-01 or after.',
1520
+ expect(validateForm({})).toBeUndefined();
1521
+ expect(validateForm({ fileInput: null })).toBeUndefined();
1522
+ });
1283
1523
  });
1284
- });
1285
1524
 
1286
- it('supports "file" field type', () => {
1287
- const result = createHeadlessForm(
1288
- JSONSchemaBuilder()
1289
- .addInput({
1290
- fileInput: mockFileInput,
1291
- })
1292
- .build()
1293
- );
1525
+ describe('when a field file is required', () => {
1526
+ it('it validates missing file correctly', () => {
1527
+ const { handleValidation } = createHeadlessForm(schemaInputTypeFile);
1528
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
1294
1529
 
1295
- expect(result).toMatchObject({
1296
- fields: [
1297
- {
1298
- type: 'file',
1299
- fileDownload: 'http://some.domain.com/file-name.pdf',
1300
- description: 'File Input Description',
1301
- fileName: 'My File',
1302
- label: 'File Input',
1303
- name: 'fileInput',
1304
- required: false,
1305
- accept: '.png,.jpg,.jpeg,.pdf',
1306
- },
1307
- ],
1530
+ expect(validateForm({})).toEqual({
1531
+ a_file: 'Required field',
1532
+ });
1533
+
1534
+ expect(
1535
+ validateForm({
1536
+ a_file: null,
1537
+ })
1538
+ ).toEqual({
1539
+ a_file: 'Required field',
1540
+ });
1541
+ });
1308
1542
  });
1309
1543
  });
1310
1544
 
@@ -1591,71 +1825,6 @@ describe('createHeadlessForm', () => {
1591
1825
  });
1592
1826
  });
1593
1827
 
1594
- it('supports "radio" field type with its "card" and "card-expandable" variants', () => {
1595
- const result = createHeadlessForm(
1596
- JSONSchemaBuilder()
1597
- .addInput({
1598
- experience_level: mockRadioCardExpandableInput,
1599
- payment_method: mockRadioCardInput,
1600
- })
1601
- .build()
1602
- );
1603
-
1604
- expect(result).toMatchObject({
1605
- fields: [
1606
- {
1607
- description:
1608
- 'Please select the experience level that aligns with this role based on the job description (not the employees overall experience)',
1609
- label: 'Experience level',
1610
- name: 'experience_level',
1611
- type: 'radio',
1612
- required: false,
1613
- variant: 'card-expandable',
1614
- options: [
1615
- {
1616
- label: 'Junior level',
1617
- value: 'junior',
1618
- description:
1619
- 'Entry level employees who perform tasks under the supervision of a more experienced employee.',
1620
- },
1621
- {
1622
- label: 'Mid level',
1623
- value: 'mid',
1624
- description:
1625
- 'Employees who perform tasks with a good degree of autonomy and/or with coordination and control functions.',
1626
- },
1627
- {
1628
- label: 'Senior level',
1629
- value: 'senior',
1630
- description:
1631
- 'Employees who perform tasks with a high degree of autonomy and/or with coordination and control functions.',
1632
- },
1633
- ],
1634
- },
1635
- {
1636
- description: 'Chose how you want to be paid',
1637
- label: 'Payment method',
1638
- name: 'payment_method',
1639
- type: 'radio',
1640
- variant: 'card',
1641
- required: false,
1642
- options: [
1643
- {
1644
- label: 'Credit Card',
1645
- value: 'cc',
1646
- description: 'Plastic money, which is still money',
1647
- },
1648
- {
1649
- label: 'Cash',
1650
- value: 'cash',
1651
- description: 'Rules Everything Around Me',
1652
- },
1653
- ],
1654
- },
1655
- ],
1656
- });
1657
- });
1658
-
1659
1828
  it('supports "null" field type', () => {
1660
1829
  const { handleValidation, fields } = createHeadlessForm(schemaInputTypeNull, {
1661
1830
  strictInputType: false,
@@ -1698,51 +1867,6 @@ describe('createHeadlessForm', () => {
1698
1867
  expect(validateForm({ username: 'john', name: null })).toBeUndefined();
1699
1868
  });
1700
1869
 
1701
- it('supports oneOf pattern validation', () => {
1702
- const result = createHeadlessForm(mockTelWithPattern);
1703
-
1704
- expect(result).toMatchObject({
1705
- fields: [
1706
- {
1707
- label: 'Phone number',
1708
- name: 'phone_number',
1709
- type: 'tel',
1710
- required: false,
1711
- options: [
1712
- {
1713
- label: 'Portugal',
1714
- pattern: '^(\\+351)[0-9]{9,}$',
1715
- },
1716
- {
1717
- label: 'United Kingdom (UK)',
1718
- pattern: '^(\\+44)[0-9]{1,}$',
1719
- },
1720
- {
1721
- label: 'Bolivia',
1722
- pattern: '^(\\+591)[0-9]{9,}$',
1723
- },
1724
- {
1725
- label: 'Canada',
1726
- pattern: '^(\\+1)(206|224)[0-9]{1,}$',
1727
- },
1728
- {
1729
- label: 'United States',
1730
- pattern: '^(\\+1)[0-9]{1,}$',
1731
- },
1732
- ],
1733
- },
1734
- ],
1735
- });
1736
-
1737
- const fieldValidator = result.fields[0].schema;
1738
-
1739
- expect(fieldValidator.isValidSync('+351123123123')).toBe(true);
1740
- expect(() => fieldValidator.validateSync('+35100')).toThrowError(
1741
- 'The option "+35100" is not valid.'
1742
- );
1743
- expect(fieldValidator.isValidSync(undefined)).toBe(true);
1744
- });
1745
-
1746
1870
  describe('supports "fieldset" field type', () => {
1747
1871
  it('supports basic case', () => {
1748
1872
  const result = createHeadlessForm({
@@ -2009,8 +2133,6 @@ describe('createHeadlessForm', () => {
2009
2133
  });
2010
2134
 
2011
2135
  it('When changing back to low work hours, the perks.food goes back to the original state', () => {
2012
- // HACK FOR NOW THAT IS CAUSING THIS EXTRA VALIDATION. Fix me!!
2013
- validateForm({});
2014
2136
  expect(
2015
2137
  validateForm({
2016
2138
  work_hours_per_week: 10,
@@ -2021,7 +2143,6 @@ describe('createHeadlessForm', () => {
2021
2143
  food: 'Required field',
2022
2144
  retirement: 'Required field',
2023
2145
  },
2024
- // pto_minimum: 20 would appear if not for the extra validation above.
2025
2146
  // ...pto is minimum error is gone! (sanity-check)
2026
2147
  });
2027
2148
 
@@ -2120,7 +2241,7 @@ describe('createHeadlessForm', () => {
2120
2241
  expect(fieldValidator.isValidSync(false)).toBe(true);
2121
2242
  expect(fieldValidator.isValidSync(undefined)).toBe(true);
2122
2243
  expect(() => fieldValidator.validateSync('foo')).toThrowError(
2123
- 'this must be a `boolean` type, but the final value was: `"foo"`.'
2244
+ 'The value must be a boolean, but received "foo"'
2124
2245
  );
2125
2246
  });
2126
2247
 
@@ -2148,6 +2269,27 @@ describe('createHeadlessForm', () => {
2148
2269
  default: true,
2149
2270
  });
2150
2271
  });
2272
+
2273
+ it('conditional: it works as undefined value', () => {
2274
+ const { fields, handleValidation } = createHeadlessForm(
2275
+ schemaInputTypeCheckboxBooleanConditional
2276
+ );
2277
+ const checkboxField = fields.find((field) => field.name === 'pet_is_cat');
2278
+
2279
+ expect(handleValidation({ has_pet: false }).formErrors).toBeUndefined();
2280
+ expect(checkboxField.isVisible).toBe(false);
2281
+
2282
+ expect(handleValidation({ has_pet: true, pet_is_cat: true }).formErrors).toBeUndefined();
2283
+ expect(checkboxField.isVisible).toBe(true);
2284
+
2285
+ expect(handleValidation({ has_pet: true, pet_is_cat: 'foo' }).formErrors).toEqual({
2286
+ pet_is_cat: 'The value must be a boolean, but received "foo"',
2287
+ });
2288
+
2289
+ // Bug: It should throw an error saying pet_is_cat is not allowed, but it doesn't.
2290
+ // Explained at "Given values from hidden fields, it does not thrown an error"
2291
+ expect(handleValidation({ has_pet: false, pet_is_cat: true }).formErrors).toBeUndefined();
2292
+ });
2151
2293
  });
2152
2294
  });
2153
2295
 
@@ -2269,43 +2411,12 @@ describe('createHeadlessForm', () => {
2269
2411
  'x-jsf-order': ['pets'],
2270
2412
  });
2271
2413
 
2272
- const fieldValidator = result.fields[0].schema;
2273
-
2274
- expect(fieldValidator.isValidSync(0)).toBe(true);
2275
- expect(fieldValidator.isValidSync(1)).toBe(true);
2276
- expect(() => fieldValidator.validateSync('2')).toThrowError('The option "2" is not valid.');
2277
- expect(fieldValidator.isValidSync(null)).toBe(true);
2278
- });
2279
-
2280
- describe('x-jsf-presentation attribute', () => {
2281
- it('support field with "x-jsf-presentation.statement"', () => {
2282
- const result = createHeadlessForm(schemaInputWithStatement);
2283
-
2284
- expect(result).toMatchObject({
2285
- fields: [
2286
- {
2287
- name: 'bonus',
2288
- label: 'Bonus',
2289
- type: 'text',
2290
- statement: {
2291
- description: 'This is a custom statement message.',
2292
- inputType: 'statement',
2293
- severity: 'info',
2294
- },
2295
- },
2296
- {
2297
- name: 'role',
2298
- label: 'Role',
2299
- type: 'text',
2300
- statement: {
2301
- description: 'This is another statement message, but more severe.',
2302
- inputType: 'statement',
2303
- severity: 'warning',
2304
- },
2305
- },
2306
- ],
2307
- });
2308
- });
2414
+ const fieldValidator = result.fields[0].schema;
2415
+
2416
+ expect(fieldValidator.isValidSync(0)).toBe(true);
2417
+ expect(fieldValidator.isValidSync(1)).toBe(true);
2418
+ expect(() => fieldValidator.validateSync('2')).toThrowError('The option "2" is not valid.');
2419
+ expect(fieldValidator.isValidSync(null)).toBe(true);
2309
2420
  });
2310
2421
 
2311
2422
  describe('property misc attributes', () => {
@@ -2367,27 +2478,93 @@ describe('createHeadlessForm', () => {
2367
2478
  });
2368
2479
  });
2369
2480
 
2370
- it('pass "description" to field', () => {
2371
- const result = createHeadlessForm({
2481
+ it('pass both root level "description" and "x-jsf-presentation.description"', () => {
2482
+ const resultsWithRootDescription = createHeadlessForm({
2372
2483
  properties: {
2373
- plain: {
2374
- title: 'Regular',
2375
- description: 'I am regular',
2376
- presentation: { inputType: 'text' },
2377
- },
2378
- html: {
2379
- title: 'Name',
2380
- description: 'I am regular',
2381
- presentation: {
2382
- description: 'I am <b>bold</b>.',
2484
+ username: mockTextInput,
2485
+ },
2486
+ required: ['username'],
2487
+ });
2488
+
2489
+ expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
2490
+
2491
+ const resultsWithPresentationDescription = createHeadlessForm({
2492
+ properties: {
2493
+ username: {
2494
+ ...mockTextInput,
2495
+ 'x-jsf-presentation': {
2383
2496
  inputType: 'text',
2497
+ // should override the root level description
2498
+ description: 'a different description with <span>markup</span>',
2384
2499
  },
2385
2500
  },
2386
2501
  },
2502
+ required: ['username'],
2387
2503
  });
2388
2504
 
2505
+ expect(resultsWithPresentationDescription.fields[0].description).toMatch(
2506
+ /a different description /i
2507
+ );
2508
+ });
2509
+
2510
+ it('pass both root level "description" and "presentation.description" (deprecated)', () => {
2511
+ const resultsWithRootDescription = createHeadlessForm({
2512
+ properties: {
2513
+ username: mockTextInputDeprecated,
2514
+ },
2515
+ required: ['username'],
2516
+ });
2517
+
2518
+ expect(resultsWithRootDescription.fields[0].description).toMatch(/your username/i);
2519
+
2520
+ const resultsWithPresentationDescription = createHeadlessForm(
2521
+ JSONSchemaBuilder()
2522
+ .addInput({
2523
+ username: {
2524
+ ...mockTextInputDeprecated,
2525
+ presentation: {
2526
+ inputType: 'text',
2527
+ maskSecret: 2,
2528
+ // should override the root level description
2529
+ description: 'a different description with <span>markup</span>',
2530
+ },
2531
+ },
2532
+ })
2533
+ .setRequiredFields(['username'])
2534
+ .build()
2535
+ );
2536
+
2537
+ expect(resultsWithPresentationDescription.fields[0].description).toMatch(
2538
+ /a different description /i
2539
+ );
2540
+ });
2541
+
2542
+ it('support field with "x-jsf-presentation.statement"', () => {
2543
+ const result = createHeadlessForm(schemaInputWithStatement);
2544
+
2389
2545
  expect(result).toMatchObject({
2390
- fields: [{ description: 'I am regular' }, { description: 'I am <b>bold</b>.' }],
2546
+ fields: [
2547
+ {
2548
+ name: 'bonus',
2549
+ label: 'Bonus',
2550
+ type: 'text',
2551
+ statement: {
2552
+ description: 'This is a custom statement message.',
2553
+ inputType: 'statement',
2554
+ severity: 'info',
2555
+ },
2556
+ },
2557
+ {
2558
+ name: 'role',
2559
+ label: 'Role',
2560
+ type: 'text',
2561
+ statement: {
2562
+ description: 'This is another statement message, but more severe.',
2563
+ inputType: 'statement',
2564
+ severity: 'warning',
2565
+ },
2566
+ },
2567
+ ],
2391
2568
  });
2392
2569
  });
2393
2570
 
@@ -2460,7 +2637,7 @@ describe('createHeadlessForm', () => {
2460
2637
  });
2461
2638
  });
2462
2639
 
2463
- it('passes scopedJsonSchema to each field', () => {
2640
+ it('pass scopedJsonSchema to each field', () => {
2464
2641
  const { fields } = createHeadlessForm(schemaWithoutInputTypes, {
2465
2642
  strictInputType: false,
2466
2643
  });
@@ -2525,7 +2702,7 @@ describe('createHeadlessForm', () => {
2525
2702
  expect(fieldsetByName).toEqual(['line_one', 'number', 'postal_code']);
2526
2703
  });
2527
2704
 
2528
- it('sorts fields based on original properties (wihout x-jsf-order)', () => {
2705
+ it('sorts fields based on original properties (without x-jsf-order)', () => {
2529
2706
  // Assert the sample schema has x-jsf-order
2530
2707
  expect(schemaWithOrderKeyword['x-jsf-order']).toBeDefined();
2531
2708
 
@@ -2546,467 +2723,276 @@ describe('createHeadlessForm', () => {
2546
2723
  });
2547
2724
  });
2548
2725
 
2549
- describe('when a field is required', () => {
2550
- let fields;
2551
- beforeEach(() => {
2552
- const result = createHeadlessForm(
2553
- buildJSONSchemaInput({ presentationFields: { inputType: 'text' }, required: true })
2554
- );
2555
- fields = result.fields;
2556
- });
2557
- describe('and value is empty', () => {
2558
- it('should throw an error', async () =>
2559
- expect(
2560
- object()
2561
- .shape({
2562
- test: fields[0].schema,
2563
- })
2564
- .validate({ test: '' })
2565
- ).rejects.toMatchObject({ errors: ['Required field'] }));
2566
- });
2567
- describe('and value is defined', () => {
2568
- it('should validate field', async () => {
2569
- const assertObj = { test: 'Hello' };
2570
- return expect(
2571
- object()
2572
- .shape({
2573
- test: fields[0].schema,
2574
- })
2575
- .validate(assertObj)
2576
- ).resolves.toEqual(assertObj);
2726
+ describe('more validations', () => {
2727
+ describe('when a field is required', () => {
2728
+ let fields;
2729
+ beforeEach(() => {
2730
+ const result = createHeadlessForm(
2731
+ buildJSONSchemaInput({ presentationFields: { inputType: 'text' }, required: true })
2732
+ );
2733
+ fields = result.fields;
2577
2734
  });
2578
- });
2579
- });
2580
-
2581
- describe('when a field is number', () => {
2582
- let fields;
2583
- beforeEach(() => {
2584
- const result = createHeadlessForm(
2585
- buildJSONSchemaInput({ presentationFields: { inputType: 'number' } })
2586
- );
2587
- fields = result.fields;
2588
- });
2589
- describe('and value is a string', () => {
2590
- it('should throw an error', async () =>
2591
- expect(
2592
- object()
2593
- .shape({
2594
- test: fields[0].schema,
2595
- })
2596
- .validate({ test: 'Hello' })
2597
- ).rejects.toThrow());
2598
- });
2599
- describe('and value is a number', () => {
2600
- it('should validate field', async () => {
2601
- const assertObj = { test: 3 };
2602
- return expect(
2603
- object()
2604
- .shape({
2605
- test: fields[0].schema,
2606
- })
2607
- .validate(assertObj)
2608
- ).resolves.toEqual(assertObj);
2735
+ describe('and value is empty', () => {
2736
+ it('should throw an error', async () =>
2737
+ expect(
2738
+ object()
2739
+ .shape({
2740
+ test: fields[0].schema,
2741
+ })
2742
+ .validate({ test: '' })
2743
+ ).rejects.toMatchObject({ errors: ['Required field'] }));
2609
2744
  });
2610
- });
2611
- describe('and maximum is set to zero', () => {
2612
- it('shows the correct validation', () => {
2613
- const { handleValidation } = createHeadlessForm(schemaInputTypeNumberZeroMaximum);
2614
- const validateForm = (vals) => friendlyError(handleValidation(vals));
2615
-
2616
- expect(validateForm({ tabs: '0' })).toBeUndefined();
2617
- expect(validateForm({ tabs: '-10' })).toBeUndefined();
2618
-
2619
- expect(validateForm({ tabs: 1 })).toEqual({
2620
- tabs: 'Must be smaller or equal to 0',
2745
+ describe('and value is defined', () => {
2746
+ it('should validate field', async () => {
2747
+ const assertObj = { test: 'Hello' };
2748
+ return expect(
2749
+ object()
2750
+ .shape({
2751
+ test: fields[0].schema,
2752
+ })
2753
+ .validate(assertObj)
2754
+ ).resolves.toEqual(assertObj);
2621
2755
  });
2622
2756
  });
2623
2757
  });
2624
- });
2625
2758
 
2626
- describe('when a field has a maxLength of 10', () => {
2627
- let fields;
2628
- beforeEach(() => {
2629
- const result = createHeadlessForm(
2630
- buildJSONSchemaInput({
2631
- presentationFields: { inputType: 'text' },
2632
- inputFields: { maxLength: 10 },
2633
- })
2634
- );
2635
- fields = result.fields;
2636
- });
2637
- describe('and value is greater than that', () => {
2638
- it('should throw an error', async () =>
2639
- expect(
2640
- object()
2641
- .shape({
2642
- test: fields[0].schema,
2643
- })
2644
- .validate({ test: 'Hello Mr John Doe' })
2645
- ).rejects.toMatchObject({ errors: ['Please insert up to 10 characters'] }));
2646
- });
2647
- describe('and value is less than that', () => {
2648
- it('should validate field', async () => {
2649
- const assertObj = { test: 'Hello John' };
2650
- return expect(
2651
- object()
2652
- .shape({
2653
- test: fields[0].schema,
2654
- })
2655
- .validate(assertObj)
2656
- ).resolves.toEqual(assertObj);
2759
+ describe('when a field is number', () => {
2760
+ let fields;
2761
+ beforeEach(() => {
2762
+ const result = createHeadlessForm(
2763
+ buildJSONSchemaInput({ presentationFields: { inputType: 'number' } })
2764
+ );
2765
+ fields = result.fields;
2657
2766
  });
2658
- });
2659
- });
2660
-
2661
- describe('when a field has a minLength of 2', () => {
2662
- let fields;
2663
- beforeEach(() => {
2664
- const result = createHeadlessForm(
2665
- buildJSONSchemaInput({
2666
- presentationFields: { inputType: 'text' },
2667
- inputFields: { minLength: 2 },
2668
- })
2669
- );
2670
- fields = result.fields;
2671
- });
2672
- describe('and value is smaller than that', () => {
2673
- it('should throw an error', async () =>
2674
- expect(
2675
- object()
2676
- .shape({
2677
- test: fields[0].schema,
2678
- })
2679
- .validate({ test: 'H' })
2680
- ).rejects.toMatchObject({ errors: ['Please insert at least 2 characters'] }));
2681
- });
2682
- describe('and value is greater than that', () => {
2683
- it('should validate field', async () => {
2684
- const assertObj = { test: 'Hello John' };
2685
- return expect(
2686
- object()
2687
- .shape({
2688
- test: fields[0].schema,
2689
- })
2690
- .validate(assertObj)
2691
- ).resolves.toEqual(assertObj);
2767
+ describe('and value is a string', () => {
2768
+ it('should throw an error', async () =>
2769
+ expect(
2770
+ object()
2771
+ .shape({
2772
+ test: fields[0].schema,
2773
+ })
2774
+ .validate({ test: 'Hello' })
2775
+ ).rejects.toThrow());
2692
2776
  });
2693
- });
2694
- });
2695
-
2696
- describe('when a field has a minimum of 0', () => {
2697
- let fields;
2698
- beforeEach(() => {
2699
- const result = createHeadlessForm(
2700
- buildJSONSchemaInput({
2701
- presentationFields: { inputType: 'number' },
2702
- inputFields: { minimum: 0 },
2703
- })
2704
- );
2705
- fields = result.fields;
2706
- });
2707
-
2708
- describe('and value is less than that', () => {
2709
- it('should throw an error', async () =>
2710
- expect(
2711
- object()
2712
- .shape({
2713
- test: fields[0].schema,
2714
- })
2715
- .validate({ test: -1 })
2716
- ).rejects.toMatchObject({ errors: ['Must be greater or equal to 0'] }));
2717
- });
2718
-
2719
- describe('and value is greater than that', () => {
2720
- it('should validate field', async () => {
2721
- const assertObj = { test: 4 };
2722
- return expect(
2723
- object()
2724
- .shape({
2725
- test: fields[0].schema,
2726
- })
2727
- .validate(assertObj)
2728
- ).resolves.toEqual(assertObj);
2777
+ describe('and value is a number', () => {
2778
+ it('should validate field', async () => {
2779
+ const assertObj = { test: 3 };
2780
+ return expect(
2781
+ object()
2782
+ .shape({
2783
+ test: fields[0].schema,
2784
+ })
2785
+ .validate(assertObj)
2786
+ ).resolves.toEqual(assertObj);
2787
+ });
2729
2788
  });
2730
- });
2731
- });
2789
+ describe('and maximum is set to zero', () => {
2790
+ it('shows the correct validation', () => {
2791
+ const { handleValidation } = createHeadlessForm(schemaInputTypeNumberZeroMaximum);
2792
+ const validateForm = (vals) => friendlyError(handleValidation(vals));
2732
2793
 
2733
- describe('when a field has a maximum of 10', () => {
2734
- let fields;
2735
- beforeEach(() => {
2736
- const result = createHeadlessForm(
2737
- buildJSONSchemaInput({
2738
- presentationFields: { inputType: 'number' },
2739
- inputFields: { maximum: 10 },
2740
- })
2741
- );
2742
- fields = result.fields;
2743
- });
2744
-
2745
- describe('and value is greater than that', () => {
2746
- it('should throw an error', async () =>
2747
- expect(
2748
- object()
2749
- .shape({
2750
- test: fields[0].schema,
2751
- })
2752
- .validate({ test: 11 })
2753
- ).rejects.toMatchObject({ errors: ['Must be smaller or equal to 10'] }));
2754
- });
2794
+ expect(validateForm({ tabs: '0' })).toBeUndefined();
2795
+ expect(validateForm({ tabs: '-10' })).toBeUndefined();
2755
2796
 
2756
- describe('and value is greater than that', () => {
2757
- it('should validate field', async () => {
2758
- const assertObj = { test: 4 };
2759
- return expect(
2760
- object()
2761
- .shape({
2762
- test: fields[0].schema,
2763
- })
2764
- .validate(assertObj)
2765
- ).resolves.toEqual(assertObj);
2797
+ expect(validateForm({ tabs: 1 })).toEqual({
2798
+ tabs: 'Must be smaller or equal to 0',
2799
+ });
2800
+ });
2766
2801
  });
2767
2802
  });
2768
- });
2769
2803
 
2770
- describe('when a field has a pattern', () => {
2771
- let fields;
2772
- beforeEach(() => {
2773
- const result = createHeadlessForm(
2774
- buildJSONSchemaInput({
2775
- presentationFields: { inputType: 'text' },
2776
- inputFields: { pattern: '^[0-9]{3}-[0-9]{2}-(?!0{4})[0-9]{4}$' },
2777
- })
2778
- );
2779
- fields = result.fields;
2780
- });
2781
- describe('and value does not match the pattern', () => {
2782
- it('should throw an error', async () =>
2783
- expect(
2784
- object()
2785
- .shape({
2786
- test: fields[0].schema,
2787
- })
2788
- .validate({ test: 'Hello' })
2789
- ).rejects.toMatchObject({ errors: [expect.any(String)] }));
2790
- });
2791
- describe('and value matches the pattern', () => {
2792
- it('should validate field', async () => {
2793
- const assertObj = { test: '401-85-1950' };
2794
- return expect(
2795
- object()
2796
- .shape({
2797
- test: fields[0].schema,
2798
- })
2799
- .validate(assertObj)
2800
- ).resolves.toEqual(assertObj);
2804
+ describe('when a field has a maxLength of 10', () => {
2805
+ let fields;
2806
+ beforeEach(() => {
2807
+ const result = createHeadlessForm(
2808
+ buildJSONSchemaInput({
2809
+ presentationFields: { inputType: 'text' },
2810
+ inputFields: { maxLength: 10 },
2811
+ })
2812
+ );
2813
+ fields = result.fields;
2814
+ });
2815
+ describe('and value is greater than that', () => {
2816
+ it('should throw an error', async () =>
2817
+ expect(
2818
+ object()
2819
+ .shape({
2820
+ test: fields[0].schema,
2821
+ })
2822
+ .validate({ test: 'Hello Mr John Doe' })
2823
+ ).rejects.toMatchObject({ errors: ['Please insert up to 10 characters'] }));
2824
+ });
2825
+ describe('and value is less than that', () => {
2826
+ it('should validate field', async () => {
2827
+ const assertObj = { test: 'Hello John' };
2828
+ return expect(
2829
+ object()
2830
+ .shape({
2831
+ test: fields[0].schema,
2832
+ })
2833
+ .validate(assertObj)
2834
+ ).resolves.toEqual(assertObj);
2835
+ });
2801
2836
  });
2802
2837
  });
2803
- });
2804
-
2805
- describe('when a field has max file size', () => {
2806
- let fields;
2807
- beforeEach(() => {
2808
- const result = createHeadlessForm(
2809
- JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
2810
- );
2811
- fields = result.fields;
2812
- });
2813
- describe('and file is greater than that', () => {
2814
- const file = new File([''], 'file.png');
2815
- Object.defineProperty(file, 'size', { value: 1024 * 1024 * 1024 });
2816
-
2817
- it('should throw an error', async () =>
2818
- expect(
2819
- object()
2820
- .shape({
2821
- fileInput: fields[0].schema,
2822
- })
2823
- .validate({ fileInput: [file] })
2824
- ).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
2825
- });
2826
- describe('and file is smaller than that', () => {
2827
- const file = new File([''], 'file.png');
2828
- Object.defineProperty(file, 'size', { value: 1024 * 1024 });
2829
-
2830
- const assertObj = { fileInput: [file] };
2831
- it('should validate field', async () =>
2832
- expect(
2833
- object()
2834
- .shape({
2835
- fileInput: fields[0].schema,
2836
- })
2837
- .validate({ fileInput: [file] })
2838
- ).resolves.toEqual(assertObj));
2839
- });
2840
- });
2841
2838
 
2842
- describe('when a field file is optional', () => {
2843
- it('it accepts an empty array', () => {
2844
- const result = createHeadlessForm(
2845
- JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
2846
- );
2847
- const emptyFile = { fileInput: [] };
2848
- expect(
2849
- object()
2850
- .shape({
2851
- fileInput: result.fields[0].schema,
2839
+ describe('when a field has a minLength of 2', () => {
2840
+ let fields;
2841
+ beforeEach(() => {
2842
+ const result = createHeadlessForm(
2843
+ buildJSONSchemaInput({
2844
+ presentationFields: { inputType: 'text' },
2845
+ inputFields: { minLength: 2 },
2852
2846
  })
2853
- .validate(emptyFile)
2854
- ).resolves.toEqual(emptyFile);
2855
- });
2856
-
2857
- it('it validates missing file correctly', () => {
2858
- const { handleValidation } = createHeadlessForm(
2859
- JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
2860
- );
2861
- const validateForm = (vals) => friendlyError(handleValidation(vals));
2862
-
2863
- expect(validateForm({})).toBeUndefined();
2864
- expect(validateForm({ fileInput: null })).toBeUndefined();
2865
- });
2866
- });
2867
-
2868
- describe('when a field file is required', () => {
2869
- it('it validates missing file correctly', () => {
2870
- const { handleValidation } = createHeadlessForm(schemaInputTypeFile);
2871
- const validateForm = (vals) => friendlyError(handleValidation(vals));
2872
-
2873
- expect(validateForm({})).toEqual({
2874
- a_file: 'Required field',
2847
+ );
2848
+ fields = result.fields;
2875
2849
  });
2876
-
2877
- expect(
2878
- validateForm({
2879
- a_file: null,
2880
- })
2881
- ).toEqual({
2882
- a_file: 'Required field',
2850
+ describe('and value is smaller than that', () => {
2851
+ it('should throw an error', async () =>
2852
+ expect(
2853
+ object()
2854
+ .shape({
2855
+ test: fields[0].schema,
2856
+ })
2857
+ .validate({ test: 'H' })
2858
+ ).rejects.toMatchObject({ errors: ['Please insert at least 2 characters'] }));
2859
+ });
2860
+ describe('and value is greater than that', () => {
2861
+ it('should validate field', async () => {
2862
+ const assertObj = { test: 'Hello John' };
2863
+ return expect(
2864
+ object()
2865
+ .shape({
2866
+ test: fields[0].schema,
2867
+ })
2868
+ .validate(assertObj)
2869
+ ).resolves.toEqual(assertObj);
2870
+ });
2883
2871
  });
2884
2872
  });
2885
- });
2886
2873
 
2887
- describe('when a field has accepted extensions', () => {
2888
- let fields;
2889
- beforeEach(() => {
2890
- const result = createHeadlessForm(
2891
- JSONSchemaBuilder().addInput({ fileInput: mockFileInput }).build()
2892
- );
2893
- fields = result.fields;
2894
- });
2895
- describe('and file is of incorrect format', () => {
2896
- const file = new File(['foo'], 'file.txt', {
2897
- type: 'text/plain',
2874
+ describe('when a field has a minimum of 0', () => {
2875
+ let fields;
2876
+ beforeEach(() => {
2877
+ const result = createHeadlessForm(
2878
+ buildJSONSchemaInput({
2879
+ presentationFields: { inputType: 'number' },
2880
+ inputFields: { minimum: 0 },
2881
+ })
2882
+ );
2883
+ fields = result.fields;
2898
2884
  });
2899
2885
 
2900
- it('should throw an error', async () =>
2901
- expect(
2902
- object()
2903
- .shape({
2904
- fileInput: fields[0].schema,
2905
- })
2906
- .validate({ fileInput: [file] })
2907
- ).rejects.toMatchObject({
2908
- errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
2909
- }));
2910
- });
2911
- describe('and file is of correct format', () => {
2912
- const file = new File(['foo'], 'file.png', {
2913
- type: 'image/png',
2886
+ describe('and value is less than that', () => {
2887
+ it('should throw an error', async () =>
2888
+ expect(
2889
+ object()
2890
+ .shape({
2891
+ test: fields[0].schema,
2892
+ })
2893
+ .validate({ test: -1 })
2894
+ ).rejects.toMatchObject({ errors: ['Must be greater or equal to 0'] }));
2914
2895
  });
2915
- Object.defineProperty(file, 'size', { value: 1024 * 1024 });
2916
2896
 
2917
- const assertObj = { fileInput: [file] };
2918
- it('should validate field', async () =>
2919
- expect(
2920
- object()
2921
- .shape({
2922
- fileInput: fields[0].schema,
2923
- })
2924
- .validate({ fileInput: [file] })
2925
- ).resolves.toEqual(assertObj));
2926
- });
2927
-
2928
- describe('and file is of correct but uppercase format ', () => {
2929
- const file = new File(['foo'], 'file.PNG', {
2930
- type: 'image/png',
2897
+ describe('and value is greater than that', () => {
2898
+ it('should validate field', async () => {
2899
+ const assertObj = { test: 4 };
2900
+ return expect(
2901
+ object()
2902
+ .shape({
2903
+ test: fields[0].schema,
2904
+ })
2905
+ .validate(assertObj)
2906
+ ).resolves.toEqual(assertObj);
2907
+ });
2931
2908
  });
2932
- Object.defineProperty(file, 'size', { value: 1024 * 1024 });
2933
-
2934
- const assertObj = { fileInput: [file] };
2935
- it('should validate field', async () =>
2936
- expect(
2937
- object()
2938
- .shape({
2939
- fileInput: fields[0].schema,
2940
- })
2941
- .validate({ fileInput: [file] })
2942
- ).resolves.toEqual(assertObj));
2943
2909
  });
2944
2910
 
2945
- describe('and file is not instance of a File', () => {
2946
- it('accepts if file object has name property', async () => {
2947
- expect(
2948
- object()
2949
- .shape({
2950
- fileInput: fields[0].schema,
2951
- })
2952
- .validate({ fileInput: [{ name: 'foo.pdf' }] })
2953
- ).resolves.toEqual({ fileInput: [{ name: 'foo.pdf' }] });
2911
+ describe('when a field has a maximum of 10', () => {
2912
+ let fields;
2913
+ beforeEach(() => {
2914
+ const result = createHeadlessForm(
2915
+ buildJSONSchemaInput({
2916
+ presentationFields: { inputType: 'number' },
2917
+ inputFields: { maximum: 10 },
2918
+ })
2919
+ );
2920
+ fields = result.fields;
2954
2921
  });
2955
2922
 
2956
- it('should validate format', async () =>
2957
- expect(
2958
- object()
2959
- .shape({
2960
- fileInput: fields[0].schema,
2961
- })
2962
- .validate({ fileInput: [{ name: 'foo.txt' }] })
2963
- ).rejects.toMatchObject({
2964
- errors: ['Unsupported file format. The acceptable formats are .png,.jpg,.jpeg,.pdf.'],
2965
- }));
2966
-
2967
- it('should validate max size', async () =>
2968
- expect(
2969
- object()
2970
- .shape({
2971
- fileInput: fields[0].schema,
2972
- })
2973
- .validate({ fileInput: [{ name: 'foo.txt', size: 1024 * 1024 * 1024 }] })
2974
- ).rejects.toMatchObject({ errors: ['File size too large. The limit is 20 MB.'] }));
2923
+ describe('and value is greater than that', () => {
2924
+ it('should throw an error', async () =>
2925
+ expect(
2926
+ object()
2927
+ .shape({
2928
+ test: fields[0].schema,
2929
+ })
2930
+ .validate({ test: 11 })
2931
+ ).rejects.toMatchObject({ errors: ['Must be smaller or equal to 10'] }));
2932
+ });
2975
2933
 
2976
- it('throw an error if invalid file object', async () =>
2977
- expect(
2978
- object()
2979
- .shape({
2980
- fileInput: fields[0].schema,
2981
- })
2982
- .validate({ fileInput: [{ path: 'foo.txt' }] })
2983
- ).rejects.toMatchObject({ errors: ['Not a valid file.'] }));
2934
+ describe('and value is greater than that', () => {
2935
+ it('should validate field', async () => {
2936
+ const assertObj = { test: 4 };
2937
+ return expect(
2938
+ object()
2939
+ .shape({
2940
+ test: fields[0].schema,
2941
+ })
2942
+ .validate(assertObj)
2943
+ ).resolves.toEqual(assertObj);
2944
+ });
2945
+ });
2984
2946
  });
2985
- });
2986
2947
 
2987
- describe('when a field has conditional presentation properties', () => {
2988
- it('returns the nested properties when the conditional matches', () => {
2989
- const { fields } = createHeadlessForm(schemaWithConditionalPresentationProperties, {
2990
- initialValues: {
2991
- // show the hidden statement
2992
- mock_radio: 'no',
2993
- },
2948
+ describe('when a field has a pattern', () => {
2949
+ let fields;
2950
+ beforeEach(() => {
2951
+ const result = createHeadlessForm(
2952
+ buildJSONSchemaInput({
2953
+ presentationFields: { inputType: 'text' },
2954
+ inputFields: { pattern: '^[0-9]{3}-[0-9]{2}-(?!0{4})[0-9]{4}$' },
2955
+ })
2956
+ );
2957
+ fields = result.fields;
2958
+ });
2959
+ describe('and value does not match the pattern', () => {
2960
+ it('should throw an error', async () =>
2961
+ expect(
2962
+ object()
2963
+ .shape({
2964
+ test: fields[0].schema,
2965
+ })
2966
+ .validate({ test: 'Hello' })
2967
+ ).rejects.toMatchObject({ errors: [expect.any(String)] }));
2968
+ });
2969
+ describe('and value matches the pattern', () => {
2970
+ it('should validate field', async () => {
2971
+ const assertObj = { test: '401-85-1950' };
2972
+ return expect(
2973
+ object()
2974
+ .shape({
2975
+ test: fields[0].schema,
2976
+ })
2977
+ .validate(assertObj)
2978
+ ).resolves.toEqual(assertObj);
2979
+ });
2994
2980
  });
2995
-
2996
- expect(fields[0].statement.description).toBe(`<a href="">conditional statement markup</a>`);
2997
2981
  });
2998
2982
  });
2999
2983
 
3000
- describe('when a JSON Schema is provided', () => {
2984
+ describe('even more validations', () => {
3001
2985
  describe('and all fields are optional', () => {
3002
2986
  let handleValidation;
3003
2987
  const validateForm = (vals) => friendlyError(handleValidation(vals));
3004
2988
 
3005
2989
  beforeEach(() => {
3006
- const result = JSONSchemaBuilder()
3007
- .addInput({ textInput: mockTextInput })
3008
- .addInput({ numberInput: mockNumberInput })
3009
- .build();
2990
+ const result = {
2991
+ properties: {
2992
+ textInput: mockTextInput,
2993
+ numberInput: mockNumberInput,
2994
+ },
2995
+ };
3010
2996
  const { handleValidation: handleValidationEach } = createHeadlessForm(result);
3011
2997
  handleValidation = handleValidationEach;
3012
2998
  });
@@ -3037,11 +3023,13 @@ describe('createHeadlessForm', () => {
3037
3023
  const validateForm = (vals) => friendlyError(handleValidation(vals));
3038
3024
 
3039
3025
  beforeEach(() => {
3040
- const result = JSONSchemaBuilder()
3041
- .addInput({ textInput: mockTextInput })
3042
- .addInput({ numberInput: mockNumberInput })
3043
- .setRequiredFields(['numberInput', 'textInput'])
3044
- .build();
3026
+ const result = {
3027
+ properties: {
3028
+ textInput: mockTextInput,
3029
+ numberInput: mockNumberInput,
3030
+ },
3031
+ required: ['numberInput', 'textInput'],
3032
+ };
3045
3033
  const { handleValidation: handleValidationEach } = createHeadlessForm(result);
3046
3034
  handleValidation = handleValidationEach;
3047
3035
  });
@@ -3868,7 +3856,19 @@ describe('createHeadlessForm', () => {
3868
3856
  });
3869
3857
  });
3870
3858
 
3871
- describe('when default values are provided', () => {
3859
+ describe('given default values', () => {
3860
+ describe('and a field with conditional presentation properties', () => {
3861
+ it('returns the nested properties when the conditional matches', () => {
3862
+ const { fields } = createHeadlessForm(schemaWithConditionalPresentationProperties, {
3863
+ initialValues: {
3864
+ // show the hidden statement
3865
+ mock_radio: 'no',
3866
+ },
3867
+ });
3868
+
3869
+ expect(fields[0].statement.description).toBe(`<a href="">conditional statement markup</a>`);
3870
+ });
3871
+ });
3872
3872
  describe('and "fieldset" has scoped conditionals', () => {
3873
3873
  it('should show conditionals fields when values fullfil conditions', () => {
3874
3874
  const result = createHeadlessForm(schemaFieldsetScopedCondition, {