@defra/forms-engine-plugin 4.0.11 → 4.0.13

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 (37) 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/models/FormModel.d.ts +2 -2
  19. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  20. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  21. package/.server/server/plugins/engine/models/types.d.ts +1 -1
  22. package/.server/server/plugins/engine/models/types.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
  25. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +0 -14
  26. package/src/server/plugins/engine/components/EastingNorthingField.ts +4 -24
  27. package/src/server/plugins/engine/components/LatLongField.test.ts +4 -24
  28. package/src/server/plugins/engine/components/LatLongField.ts +8 -33
  29. package/src/server/plugins/engine/components/LocationFieldBase.ts +1 -15
  30. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +22 -8
  31. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -20
  32. package/src/server/plugins/engine/components/NumberField.test.ts +12 -504
  33. package/src/server/plugins/engine/components/NumberField.ts +4 -133
  34. package/src/server/plugins/engine/components/OsGridRefField.test.ts +11 -33
  35. package/src/server/plugins/engine/components/OsGridRefField.ts +5 -38
  36. package/src/server/plugins/engine/models/FormModel.ts +1 -1
  37. package/src/server/plugins/engine/models/types.ts +1 -1
@@ -26,19 +26,6 @@ import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
26
26
  // Precision constants
27
27
  // UK latitude/longitude requires high precision for accurate location (within ~11mm)
28
28
  const DECIMAL_PRECISION = 7 // 7 decimal places
29
- const MIN_DECIMAL_PLACES = 1 // At least 1 decimal place required
30
-
31
- // Latitude length constraints
32
- // Min: 3 chars for values like "52.1" (2 digits + decimal + 1 decimal place)
33
- // Max: 10 chars for values like "59.1234567" (2 digits + decimal + 7 decimal places)
34
- const LATITUDE_MIN_LENGTH = 3
35
- const LATITUDE_MAX_LENGTH = 10
36
-
37
- // Longitude length constraints
38
- // Min: 2 chars for values like "-1" or single digit with decimal (needs min decimal places)
39
- // Max: 10 chars for values like "-1.1234567" (minus + 1 digit + decimal + 7 decimal places)
40
- const LONGITUDE_MIN_LENGTH = 2
41
- const LONGITUDE_MAX_LENGTH = 10
42
29
 
43
30
  export class LatLongField extends FormComponent {
44
31
  declare options: LatLongFieldComponent['options']
@@ -57,10 +44,10 @@ export class LatLongField extends FormComponent {
57
44
  const isRequired = options.required !== false
58
45
 
59
46
  // Read schema values from def.schema with fallback defaults
60
- const latitudeMin = schema?.latitude?.min ?? 49
61
- const latitudeMax = schema?.latitude?.max ?? 60
62
- const longitudeMin = schema?.longitude?.min ?? -9
63
- const longitudeMax = schema?.longitude?.max ?? 2
47
+ const latitudeMin = schema?.latitude?.min ?? 49.85
48
+ const latitudeMax = schema?.latitude?.max ?? 60.859
49
+ const longitudeMin = schema?.longitude?.min ?? -13.687
50
+ const longitudeMax = schema?.longitude?.max ?? 1.767
64
51
 
65
52
  const customValidationMessages: LanguageMessages =
66
53
  convertToLanguageMessages({
@@ -68,8 +55,6 @@ export class LatLongField extends FormComponent {
68
55
  'number.base': messageTemplate.objectMissing,
69
56
  'number.precision':
70
57
  '{{#label}} must have no more than 7 decimal places',
71
- 'number.minPrecision':
72
- '{{#label}} must have at least {{#minPrecision}} decimal place',
73
58
  'number.unsafe': '{{#label}} must be a valid number'
74
59
  })
75
60
 
@@ -77,18 +62,14 @@ export class LatLongField extends FormComponent {
77
62
  ...customValidationMessages,
78
63
  'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,
79
64
  'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,
80
- 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,
81
- 'number.minLength': `Latitude for ${this.title} must be between 3 and 10 characters`,
82
- 'number.maxLength': `Latitude for ${this.title} must be between 3 and 10 characters`
65
+ 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`
83
66
  })
84
67
 
85
68
  const longitudeMessages: LanguageMessages = convertToLanguageMessages({
86
69
  ...customValidationMessages,
87
70
  'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,
88
71
  'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,
89
- 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,
90
- 'number.minLength': `Longitude for ${this.title} must be between 2 and 10 characters`,
91
- 'number.maxLength': `Longitude for ${this.title} must be between 2 and 10 characters`
72
+ 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`
92
73
  })
93
74
 
94
75
  this.collection = new ComponentCollection(
@@ -100,10 +81,7 @@ export class LatLongField extends FormComponent {
100
81
  schema: {
101
82
  min: latitudeMin,
102
83
  max: latitudeMax,
103
- precision: DECIMAL_PRECISION,
104
- minPrecision: MIN_DECIMAL_PLACES,
105
- minLength: LATITUDE_MIN_LENGTH,
106
- maxLength: LATITUDE_MAX_LENGTH
84
+ precision: DECIMAL_PRECISION
107
85
  },
108
86
  options: {
109
87
  required: isRequired,
@@ -120,10 +98,7 @@ export class LatLongField extends FormComponent {
120
98
  schema: {
121
99
  min: longitudeMin,
122
100
  max: longitudeMax,
123
- precision: DECIMAL_PRECISION,
124
- minPrecision: MIN_DECIMAL_PLACES,
125
- minLength: LONGITUDE_MIN_LENGTH,
126
- maxLength: LONGITUDE_MAX_LENGTH
101
+ precision: DECIMAL_PRECISION
127
102
  },
128
103
  options: {
129
104
  required: isRequired,
@@ -28,11 +28,6 @@ interface LocationFieldOptions {
28
28
  interface ValidationConfig {
29
29
  pattern: RegExp
30
30
  patternErrorMessage: string
31
- customValidation?: (
32
- value: string,
33
- helpers: joi.CustomHelpers
34
- ) => string | joi.ErrorReport
35
- additionalMessages?: LanguageMessages
36
31
  }
37
32
 
38
33
  /**
@@ -71,14 +66,9 @@ export abstract class LocationFieldBase extends FormComponent {
71
66
  .required()
72
67
  .pattern(config.pattern)
73
68
  .messages({
74
- 'string.pattern.base': config.patternErrorMessage,
75
- ...config.additionalMessages
69
+ 'string.pattern.base': config.patternErrorMessage
76
70
  })
77
71
 
78
- if (config.customValidation) {
79
- formSchema = formSchema.custom(config.customValidation)
80
- }
81
-
82
72
  if (locationOptions.required === false) {
83
73
  formSchema = formSchema.allow('')
84
74
  }
@@ -91,10 +81,6 @@ export abstract class LocationFieldBase extends FormComponent {
91
81
  'string.pattern.base'
92
82
  ]
93
83
 
94
- if (config.additionalMessages) {
95
- messageKeys.push(...Object.keys(config.additionalMessages))
96
- }
97
-
98
84
  const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {
99
85
  acc[key] = message
100
86
  return acc
@@ -99,13 +99,27 @@ describe('NationalGridFieldNumberField', () => {
99
99
  })
100
100
 
101
101
  it('accepts valid values', () => {
102
- const result1 = collection.validate(getFormData('NG12345678'))
103
- const result2 = collection.validate(getFormData('ng12345678'))
104
- const result3 = collection.validate(getFormData('AB98765432'))
102
+ // Test 8-digit parcel ID format (2x4)
103
+ const result1 = collection.validate(getFormData('TQ12345678'))
104
+ const result2 = collection.validate(getFormData('TQ 1234 5678'))
105
+
106
+ // Test 10-digit OS grid reference format (2x5)
107
+ const result3 = collection.validate(getFormData('SU1234567890'))
108
+ const result4 = collection.validate(getFormData('SU 12345 67890'))
109
+
110
+ expect(result1.errors).toBeUndefined()
111
+ expect(result2.errors).toBeUndefined()
112
+ expect(result3.errors).toBeUndefined()
113
+ expect(result4.errors).toBeUndefined()
114
+
115
+ // Test case-insensitive
116
+ const result5 = collection.validate(getFormData('nt12345678'))
105
117
 
106
118
  expect(result1.errors).toBeUndefined()
107
119
  expect(result2.errors).toBeUndefined()
108
120
  expect(result3.errors).toBeUndefined()
121
+ expect(result4.errors).toBeUndefined()
122
+ expect(result5.errors).toBeUndefined()
109
123
  })
110
124
 
111
125
  it('formats values with spaces per GDS guidance', () => {
@@ -114,8 +128,8 @@ describe('NationalGridFieldNumberField', () => {
114
128
  const result3 = collection.validate(getFormData('NG12345,678'))
115
129
 
116
130
  expect(result1.value.myComponent).toBe('NG 1234 5678')
117
- expect(result2.value.myComponent).toBe('NG 1234 5678')
118
- expect(result3.value.myComponent).toBe('NG 1234 5678')
131
+ expect(result2.value.myComponent).toBe('NG12345678')
132
+ expect(result3.value.myComponent).toBe('NG12345,678')
119
133
  })
120
134
 
121
135
  it('adds errors for empty value', () => {
@@ -258,15 +272,15 @@ describe('NationalGridFieldNumberField', () => {
258
272
  assertions: [
259
273
  {
260
274
  input: getFormData(' NG12345678'),
261
- output: { value: getFormData('NG 1234 5678') }
275
+ output: { value: getFormData('NG12345678') }
262
276
  },
263
277
  {
264
278
  input: getFormData('NG12345678 '),
265
- output: { value: getFormData('NG 1234 5678') }
279
+ output: { value: getFormData('NG12345678') }
266
280
  },
267
281
  {
268
282
  input: getFormData(' NG12345678 \n\n'),
269
- output: { value: getFormData('NG 1234 5678') }
283
+ output: { value: getFormData('NG12345678') }
270
284
  }
271
285
  ]
272
286
  },
@@ -1,5 +1,4 @@
1
1
  import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
2
- import type joi from 'joi'
3
2
 
4
3
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
5
4
 
@@ -7,26 +6,16 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
7
6
  declare options: NationalGridFieldNumberFieldComponent['options']
8
7
 
9
8
  protected getValidationConfig() {
10
- return {
11
- // Pattern allows spaces and commas in the input since custom validation will clean them
12
- pattern: /^[A-Z]{2}[\d\s,]*$/i,
13
- patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`,
14
- customValidation: (value: string, helpers: joi.CustomHelpers) => {
15
- // Strip spaces and commas for validation
16
- const cleanValue = value.replace(/[\s,]/g, '')
17
-
18
- // Check if it matches the exact pattern after cleaning
19
- if (!/^[A-Z]{2}\d{8}$/i.test(cleanValue)) {
20
- return helpers.error('string.pattern.base')
21
- }
22
-
23
- // Format with spaces per GDS guidance: NG 1234 5678
24
- const letters = cleanValue.substring(0, 2)
25
- const numbers = cleanValue.substring(2)
26
- const formattedValue = `${letters} ${numbers.substring(0, 4)} ${numbers.substring(4)}`
9
+ // Regex for OS grid references and parcel IDs
10
+ // Validates specific valid OS grid letter combinations with:
11
+ // - 2 letters & 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789
12
+ // - 2 letters & 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345
13
+ const pattern =
14
+ /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/
27
15
 
28
- return formattedValue
29
- }
16
+ return {
17
+ pattern,
18
+ patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`
30
19
  }
31
20
  }
32
21