@defra/forms-engine-plugin 4.0.10 → 4.0.12

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.
Files changed (35) hide show
  1. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
  2. package/.server/server/plugins/engine/components/EastingNorthingField.js +4 -24
  3. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  4. package/.server/server/plugins/engine/components/LatLongField.js +8 -31
  5. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  6. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +1 -3
  7. package/.server/server/plugins/engine/components/LocationFieldBase.js +1 -8
  8. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  9. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +0 -2
  10. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +7 -18
  11. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  12. package/.server/server/plugins/engine/components/NumberField.d.ts +0 -18
  13. package/.server/server/plugins/engine/components/NumberField.js +2 -103
  14. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +0 -2
  16. package/.server/server/plugins/engine/components/OsGridRefField.js +4 -32
  17. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  18. package/.server/server/plugins/engine/pageControllers/helpers/pages.d.ts +1 -2
  19. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +1 -11
  20. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
  23. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +0 -14
  24. package/src/server/plugins/engine/components/EastingNorthingField.ts +4 -24
  25. package/src/server/plugins/engine/components/LatLongField.test.ts +4 -24
  26. package/src/server/plugins/engine/components/LatLongField.ts +8 -33
  27. package/src/server/plugins/engine/components/LocationFieldBase.ts +1 -15
  28. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +22 -8
  29. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -20
  30. package/src/server/plugins/engine/components/NumberField.test.ts +12 -504
  31. package/src/server/plugins/engine/components/NumberField.ts +4 -133
  32. package/src/server/plugins/engine/components/OsGridRefField.test.ts +11 -33
  33. package/src/server/plugins/engine/components/OsGridRefField.ts +5 -38
  34. package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -24
  35. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +0 -15
@@ -1,11 +1,7 @@
1
1
  import { ComponentType, type NumberFieldComponent } from '@defra/forms-model'
2
2
 
3
3
  import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
4
- import {
5
- NumberField,
6
- validateMinimumPrecision,
7
- validateStringLength
8
- } from '~/src/server/plugins/engine/components/NumberField.js'
4
+ import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
9
5
  import {
10
6
  getAnswer,
11
7
  type Field
@@ -23,94 +19,6 @@ describe('NumberField', () => {
23
19
  })
24
20
  })
25
21
 
26
- describe('Helper Functions', () => {
27
- describe('validateStringLength', () => {
28
- it('returns valid when no constraints provided', () => {
29
- expect(validateStringLength(123)).toEqual({ isValid: true })
30
- expect(validateStringLength(123, undefined, undefined)).toEqual({
31
- isValid: true
32
- })
33
- })
34
-
35
- it('validates minimum length correctly', () => {
36
- expect(validateStringLength(12, 3)).toEqual({
37
- isValid: false,
38
- error: 'minLength'
39
- })
40
- expect(validateStringLength(123, 3)).toEqual({ isValid: true })
41
- expect(validateStringLength(1234, 3)).toEqual({ isValid: true })
42
- })
43
-
44
- it('validates maximum length correctly', () => {
45
- expect(validateStringLength(123456, undefined, 5)).toEqual({
46
- isValid: false,
47
- error: 'maxLength'
48
- })
49
- expect(validateStringLength(12345, undefined, 5)).toEqual({
50
- isValid: true
51
- })
52
- expect(validateStringLength(123, undefined, 5)).toEqual({
53
- isValid: true
54
- })
55
- })
56
-
57
- it('validates both min and max length', () => {
58
- expect(validateStringLength(12, 3, 5)).toEqual({
59
- isValid: false,
60
- error: 'minLength'
61
- })
62
- expect(validateStringLength(123456, 3, 5)).toEqual({
63
- isValid: false,
64
- error: 'maxLength'
65
- })
66
- expect(validateStringLength(1234, 3, 5)).toEqual({ isValid: true })
67
- })
68
-
69
- it('handles decimal numbers correctly', () => {
70
- // "52.1" = 4 characters
71
- expect(validateStringLength(52.1, 3, 5)).toEqual({ isValid: true })
72
- // "52.123456" = 9 characters
73
- expect(validateStringLength(52.123456, undefined, 8)).toEqual({
74
- isValid: false,
75
- error: 'maxLength'
76
- })
77
- })
78
-
79
- it('handles negative numbers correctly', () => {
80
- // "-1.5" = 4 characters
81
- expect(validateStringLength(-1.5, 3, 5)).toEqual({ isValid: true })
82
- // "-9.1234567" = 10 characters
83
- expect(validateStringLength(-9.1234567, undefined, 9)).toEqual({
84
- isValid: false,
85
- error: 'maxLength'
86
- })
87
- })
88
- })
89
-
90
- describe('validateMinimumPrecision', () => {
91
- it('returns false for integers', () => {
92
- expect(validateMinimumPrecision(52, 1)).toBe(false)
93
- expect(validateMinimumPrecision(100, 2)).toBe(false)
94
- })
95
-
96
- it('validates minimum precision correctly', () => {
97
- expect(validateMinimumPrecision(52.1, 1)).toBe(true)
98
- expect(validateMinimumPrecision(52.12, 2)).toBe(true)
99
- expect(validateMinimumPrecision(52.123, 3)).toBe(true)
100
- })
101
-
102
- it('returns false when precision is insufficient', () => {
103
- expect(validateMinimumPrecision(52.1, 2)).toBe(false)
104
- expect(validateMinimumPrecision(52.12, 3)).toBe(false)
105
- })
106
-
107
- it('handles exact precision requirement', () => {
108
- expect(validateMinimumPrecision(52.12345, 5)).toBe(true)
109
- expect(validateMinimumPrecision(52.1234, 5)).toBe(false)
110
- })
111
- })
112
- })
113
-
114
22
  describe('Defaults', () => {
115
23
  let def: NumberFieldComponent
116
24
  let collection: ComponentCollection
@@ -596,184 +504,17 @@ describe('NumberField', () => {
596
504
  ]
597
505
  },
598
506
  {
599
- description: 'Schema minPrecision (minimum 1 decimal place)',
600
- component: createPrecisionTestComponent(1),
601
- assertions: [
602
- {
603
- input: getFormData('52'),
604
- output: {
605
- value: getFormData(52),
606
- errors: [
607
- expect.objectContaining({
608
- text: 'Example number field must have at least 1 decimal place'
609
- })
610
- ]
611
- }
612
- },
613
- {
614
- input: getFormData('52.0'),
615
- output: {
616
- value: getFormData(52),
617
- errors: [
618
- expect.objectContaining({
619
- text: 'Example number field must have at least 1 decimal place'
620
- })
621
- ]
622
- }
623
- },
624
- {
625
- input: getFormData('52.1'),
626
- output: { value: getFormData(52.1) }
627
- },
628
- {
629
- input: getFormData('52.123456'),
630
- output: { value: getFormData(52.123456) }
631
- }
632
- ]
633
- },
634
- {
635
- description: 'Schema minPrecision (minimum 2 decimal places)',
636
- component: createPrecisionTestComponent(2),
637
- assertions: [
638
- {
639
- input: getFormData('52.1'),
640
- output: {
641
- value: getFormData(52.1),
642
- errors: [
643
- expect.objectContaining({
644
- text: 'Example number field must have at least 2 decimal places'
645
- })
646
- ]
647
- }
648
- },
649
- {
650
- input: getFormData('52.12'),
651
- output: { value: getFormData(52.12) }
652
- },
653
- {
654
- input: getFormData('52.1234567'),
655
- output: { value: getFormData(52.1234567) }
656
- }
657
- ]
658
- },
659
- {
660
- description: 'Schema minLength (minimum 3 characters)',
661
- component: createLengthTestComponent(3, undefined),
662
- assertions: [
663
- {
664
- input: getFormData('12'),
665
- output: {
666
- value: getFormData(12),
667
- errors: [
668
- expect.objectContaining({
669
- text: 'Example number field must be at least 3 characters'
670
- })
671
- ]
672
- }
673
- },
674
- {
675
- input: getFormData('123'),
676
- output: { value: getFormData(123) }
677
- },
678
- {
679
- input: getFormData('1234'),
680
- output: { value: getFormData(1234) }
681
- }
682
- ]
683
- },
684
- {
685
- description: 'Schema maxLength (maximum 5 characters)',
686
- component: createLengthTestComponent(undefined, 5),
687
- assertions: [
688
- {
689
- input: getFormData('123456'),
690
- output: {
691
- value: getFormData(123456),
692
- errors: [
693
- expect.objectContaining({
694
- text: 'Example number field must be no more than 5 characters'
695
- })
696
- ]
697
- }
698
- },
699
- {
700
- input: getFormData('12345'),
701
- output: { value: getFormData(12345) }
702
- },
703
- {
704
- input: getFormData('123'),
705
- output: { value: getFormData(123) }
706
- }
707
- ]
708
- },
709
- {
710
- description:
711
- 'Schema minLength and maxLength (3-8 characters, like latitude)',
507
+ description: 'Schema min and max',
712
508
  component: {
713
- title: 'Latitude field',
714
- shortDescription: 'Latitude',
509
+ title: 'Example number field',
715
510
  name: 'myComponent',
716
511
  type: ComponentType.NumberField,
717
- options: {
718
- customValidationMessages: {
719
- 'number.minPrecision':
720
- '{{#label}} must have at least {{#minPrecision}} decimal place',
721
- 'number.minLength':
722
- '{{#label}} must be between 3 and 10 characters',
723
- 'number.maxLength':
724
- '{{#label}} must be between 3 and 10 characters'
725
- }
726
- },
727
- schema: {
728
- min: 49,
729
- max: 60,
730
- precision: 7,
731
- minPrecision: 1,
732
- minLength: 3,
733
- maxLength: 10
734
- }
735
- } as NumberFieldComponent,
736
- assertions: [
737
- {
738
- input: getFormData('52'),
739
- output: {
740
- value: getFormData(52),
741
- errors: [
742
- expect.objectContaining({
743
- text: 'Latitude must have at least 1 decimal place'
744
- })
745
- ]
746
- }
747
- },
748
- {
749
- input: getFormData('52.12345678'),
750
- output: {
751
- value: getFormData(52.12345678),
752
- errors: [
753
- expect.objectContaining({
754
- text: 'Latitude must have 7 or fewer decimal places'
755
- })
756
- ]
757
- }
758
- },
759
- {
760
- input: getFormData('52.1'),
761
- output: { value: getFormData(52.1) }
762
- },
763
- {
764
- input: getFormData('52.1234'),
765
- output: { value: getFormData(52.1234) }
766
- }
767
- ]
768
- },
769
- {
770
- description: 'Schema min and max',
771
- component: createNumberComponent({
512
+ options: {},
772
513
  schema: {
773
514
  min: 5,
774
515
  max: 8
775
516
  }
776
- }),
517
+ } satisfies NumberFieldComponent,
777
518
  assertions: [
778
519
  {
779
520
  input: getFormData('4'),
@@ -801,7 +542,10 @@ describe('NumberField', () => {
801
542
  },
802
543
  {
803
544
  description: 'Custom validation message',
804
- component: createNumberComponent({
545
+ component: {
546
+ title: 'Example number field',
547
+ name: 'myComponent',
548
+ type: ComponentType.NumberField,
805
549
  options: {
806
550
  customValidationMessage: 'This is a custom error',
807
551
  customValidationMessages: {
@@ -810,8 +554,9 @@ describe('NumberField', () => {
810
554
  'number.min': 'This is not used',
811
555
  'number.max': 'This is not used'
812
556
  }
813
- }
814
- }),
557
+ },
558
+ schema: {}
559
+ } satisfies NumberFieldComponent,
815
560
  assertions: [
816
561
  {
817
562
  input: getFormData(''),
@@ -941,45 +686,6 @@ describe('NumberField', () => {
941
686
  }
942
687
  ]
943
688
  },
944
- {
945
- description: 'Custom validation message overrides length validation',
946
- component: {
947
- title: 'Example number field',
948
- name: 'myComponent',
949
- type: ComponentType.NumberField,
950
- options: {
951
- customValidationMessage: 'This is a custom length error'
952
- },
953
- schema: {
954
- minLength: 3,
955
- maxLength: 5
956
- }
957
- } satisfies NumberFieldComponent,
958
- assertions: [
959
- {
960
- input: getFormData('12'),
961
- output: {
962
- value: getFormData(12),
963
- errors: [
964
- expect.objectContaining({
965
- text: 'This is a custom length error'
966
- })
967
- ]
968
- }
969
- },
970
- {
971
- input: getFormData('123456'),
972
- output: {
973
- value: getFormData(123456),
974
- errors: [
975
- expect.objectContaining({
976
- text: 'This is a custom length error'
977
- })
978
- ]
979
- }
980
- }
981
- ]
982
- },
983
689
  {
984
690
  description: 'Optional field',
985
691
  component: {
@@ -1014,202 +720,4 @@ describe('NumberField', () => {
1014
720
  )
1015
721
  })
1016
722
  })
1017
-
1018
- describe('Edge cases', () => {
1019
- let collection: ComponentCollection
1020
-
1021
- beforeEach(() => {
1022
- const def = createNumberComponent({
1023
- schema: {
1024
- min: -100,
1025
- max: 100,
1026
- precision: 2
1027
- }
1028
- })
1029
- collection = new ComponentCollection([def], { model })
1030
- })
1031
-
1032
- it('handles negative numbers correctly', () => {
1033
- const result = collection.validate(getFormData('-50.5'))
1034
- expect(result).toEqual({
1035
- value: getFormData(-50.5)
1036
- })
1037
- })
1038
-
1039
- it('handles zero correctly', () => {
1040
- const result = collection.validate(getFormData('0'))
1041
- expect(result).toEqual({
1042
- value: getFormData(0)
1043
- })
1044
- })
1045
-
1046
- it('handles zero with decimal correctly', () => {
1047
- const result = collection.validate(getFormData('0.0'))
1048
- expect(result).toEqual({
1049
- value: getFormData(0)
1050
- })
1051
- })
1052
-
1053
- it('handles negative zero correctly', () => {
1054
- const result = collection.validate(getFormData('-0'))
1055
- expect(result).toEqual({
1056
- value: getFormData(0)
1057
- })
1058
- })
1059
-
1060
- it('handles scientific notation (parsed as number, may fail range)', () => {
1061
- // JavaScript parses '1e10' as 10000000000, which exceeds max of 100
1062
- const result = collection.validate(getFormData('1e10'))
1063
- expect(result).toEqual({
1064
- value: getFormData(10000000000),
1065
- errors: [
1066
- expect.objectContaining({
1067
- text: 'Example number field must be 100 or lower'
1068
- })
1069
- ]
1070
- })
1071
- })
1072
-
1073
- it('handles scientific notation with negative exponent (parsed as number)', () => {
1074
- // JavaScript parses '1e-5' as 0.00001, which fails precision check (5 decimal places > 2)
1075
- const result = collection.validate(getFormData('1e-5'))
1076
- expect(result.value).toEqual(getFormData(0.00001))
1077
- expect(result.errors).toBeDefined()
1078
- expect(result.errors?.[0]).toMatchObject({
1079
- text: 'Example number field must have 2 or fewer decimal places'
1080
- })
1081
- })
1082
-
1083
- it('handles large negative numbers', () => {
1084
- const result = collection.validate(getFormData('-99.99'))
1085
- expect(result).toEqual({
1086
- value: getFormData(-99.99)
1087
- })
1088
- })
1089
-
1090
- it('handles numbers at boundary limits', () => {
1091
- const maxResult = collection.validate(getFormData('100'))
1092
- expect(maxResult).toEqual({
1093
- value: getFormData(100)
1094
- })
1095
-
1096
- const minResult = collection.validate(getFormData('-100'))
1097
- expect(minResult).toEqual({
1098
- value: getFormData(-100)
1099
- })
1100
- })
1101
-
1102
- describe('with length constraints', () => {
1103
- beforeEach(() => {
1104
- const def = createNumberComponent({
1105
- schema: {
1106
- min: -9,
1107
- max: 9,
1108
- precision: 7,
1109
- minPrecision: 1,
1110
- minLength: 2,
1111
- maxLength: 10
1112
- },
1113
- options: {
1114
- customValidationMessages: {
1115
- 'number.minPrecision':
1116
- 'Example number field must have at least {{minPrecision}} decimal place',
1117
- 'number.precision':
1118
- 'Example number field must have no more than {{limit}} decimal places',
1119
- 'number.minLength':
1120
- 'Example number field must be at least {{minLength}} characters',
1121
- 'number.maxLength':
1122
- 'Example number field must be no more than {{maxLength}} characters'
1123
- }
1124
- }
1125
- })
1126
- collection = new ComponentCollection([def], { model })
1127
- })
1128
-
1129
- it('validates negative numbers with decimals', () => {
1130
- const result = collection.validate(getFormData('-5.1234567'))
1131
- expect(result).toEqual({
1132
- value: getFormData(-5.1234567)
1133
- })
1134
- })
1135
-
1136
- it('rejects negative numbers that are too short', () => {
1137
- const result = collection.validate(getFormData('-5'))
1138
- expect(result.value).toEqual(getFormData(-5))
1139
- expect(result.errors).toBeDefined()
1140
- expect(result.errors?.[0].text).toContain('decimal place')
1141
- })
1142
-
1143
- it('rejects numbers with too many characters', () => {
1144
- const result = collection.validate(getFormData('-5.12345678'))
1145
- expect(result.value).toEqual(getFormData(-5.12345678))
1146
- expect(result.errors).toBeDefined()
1147
- expect(result.errors?.[0].text).toContain('decimal places')
1148
- })
1149
- })
1150
- })
1151
723
  })
1152
-
1153
- /**
1154
- * Factory function to create a default NumberField component with optional overrides
1155
- */
1156
- function createNumberComponent(
1157
- overrides: Partial<NumberFieldComponent> = {}
1158
- ): NumberFieldComponent {
1159
- const base = {
1160
- title: 'Example number field',
1161
- name: 'myComponent',
1162
- type: ComponentType.NumberField,
1163
- options: {},
1164
- schema: {}
1165
- } satisfies NumberFieldComponent
1166
-
1167
- // Deep merge for nested objects like options and schema
1168
- return {
1169
- ...base,
1170
- ...overrides,
1171
- options: { ...base.options, ...(overrides.options ?? {}) },
1172
- schema: { ...base.schema, ...(overrides.schema ?? {}) }
1173
- } satisfies NumberFieldComponent
1174
- }
1175
-
1176
- /**
1177
- * Helper for precision validation tests
1178
- */
1179
- function createPrecisionTestComponent(
1180
- minPrecision: number,
1181
- precision = 7
1182
- ): NumberFieldComponent {
1183
- const pluralSuffix = minPrecision > 1 ? 's' : ''
1184
- return createNumberComponent({
1185
- options: {
1186
- customValidationMessages: {
1187
- 'number.minPrecision': `{{#label}} must have at least {{#minPrecision}} decimal place${pluralSuffix}`
1188
- }
1189
- },
1190
- schema: { precision, minPrecision }
1191
- })
1192
- }
1193
-
1194
- /**
1195
- * Helper for length validation tests
1196
- */
1197
- function createLengthTestComponent(
1198
- minLength?: number,
1199
- maxLength?: number
1200
- ): NumberFieldComponent {
1201
- const messages: Record<string, string> = {}
1202
- if (minLength) {
1203
- messages['number.minLength'] =
1204
- '{{#label}} must be at least {{#minLength}} characters'
1205
- }
1206
- if (maxLength) {
1207
- messages['number.maxLength'] =
1208
- '{{#label}} must be no more than {{#maxLength}} characters'
1209
- }
1210
-
1211
- return createNumberComponent({
1212
- options: { customValidationMessages: messages },
1213
- schema: { minLength, maxLength }
1214
- })
1215
- }