@defra/forms-engine-plugin 4.0.12 → 4.0.14

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 (44) hide show
  1. package/.public/stylesheets/application.min.css +1 -1
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/application.scss +0 -1
  4. package/.server/client/stylesheets/shared.scss +0 -1
  5. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +4 -2
  6. package/.server/server/plugins/engine/components/EastingNorthingField.js +13 -12
  7. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  8. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +7 -1
  9. package/.server/server/plugins/engine/components/LatLongField.js +7 -6
  10. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  11. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +3 -9
  12. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  13. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +5 -5
  14. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/OsGridRefField.js +8 -4
  16. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  17. package/.server/server/plugins/engine/components/types.d.ts +3 -0
  18. package/.server/server/plugins/engine/components/types.js.map +1 -1
  19. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -2
  20. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  21. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  22. package/.server/server/plugins/engine/models/types.d.ts +1 -1
  23. package/.server/server/plugins/engine/models/types.js.map +1 -1
  24. package/.server/server/plugins/engine/views/components/_location-field-base.html +22 -14
  25. package/package.json +2 -2
  26. package/src/client/stylesheets/application.scss +0 -1
  27. package/src/client/stylesheets/shared.scss +0 -1
  28. package/src/server/forms/register-as-a-unicorn-breeder.yaml +4 -2
  29. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +9 -3
  30. package/src/server/plugins/engine/components/EastingNorthingField.ts +13 -12
  31. package/src/server/plugins/engine/components/LatLongField.test.ts +9 -3
  32. package/src/server/plugins/engine/components/LatLongField.ts +7 -6
  33. package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +14 -5
  34. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +3 -9
  35. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +9 -12
  36. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +5 -5
  37. package/src/server/plugins/engine/components/OsGridRefField.test.ts +19 -6
  38. package/src/server/plugins/engine/components/OsGridRefField.ts +8 -4
  39. package/src/server/plugins/engine/components/types.ts +3 -0
  40. package/src/server/plugins/engine/models/FormModel.ts +1 -1
  41. package/src/server/plugins/engine/models/types.ts +1 -1
  42. package/src/server/plugins/engine/views/components/_location-field-base.html +22 -14
  43. package/.server/client/stylesheets/_location-input.scss +0 -60
  44. package/src/client/stylesheets/_location-input.scss +0 -60
@@ -103,23 +103,15 @@ describe('NationalGridFieldNumberField', () => {
103
103
  const result1 = collection.validate(getFormData('TQ12345678'))
104
104
  const result2 = collection.validate(getFormData('TQ 1234 5678'))
105
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
106
  expect(result1.errors).toBeUndefined()
111
107
  expect(result2.errors).toBeUndefined()
112
- expect(result3.errors).toBeUndefined()
113
- expect(result4.errors).toBeUndefined()
114
108
 
115
109
  // Test case-insensitive
116
- const result5 = collection.validate(getFormData('nt12345678'))
110
+ const result3 = collection.validate(getFormData('nt12345678'))
117
111
 
118
112
  expect(result1.errors).toBeUndefined()
119
113
  expect(result2.errors).toBeUndefined()
120
114
  expect(result3.errors).toBeUndefined()
121
- expect(result4.errors).toBeUndefined()
122
- expect(result5.errors).toBeUndefined()
123
115
  })
124
116
 
125
117
  it('formats values with spaces per GDS guidance', () => {
@@ -265,6 +257,7 @@ describe('NationalGridFieldNumberField', () => {
265
257
  description: 'Trim empty spaces',
266
258
  component: {
267
259
  title: 'Example National Grid field number',
260
+ shortDescription: 'Grid field',
268
261
  name: 'myComponent',
269
262
  type: ComponentType.NationalGridFieldNumberField,
270
263
  options: {}
@@ -288,6 +281,7 @@ describe('NationalGridFieldNumberField', () => {
288
281
  description: 'Pattern validation',
289
282
  component: {
290
283
  title: 'Example National Grid field number',
284
+ shortDescription: 'Grid field',
291
285
  name: 'myComponent',
292
286
  type: ComponentType.NationalGridFieldNumberField,
293
287
  options: {}
@@ -299,7 +293,7 @@ describe('NationalGridFieldNumberField', () => {
299
293
  value: getFormData('NG1234567'),
300
294
  errors: expect.arrayContaining([
301
295
  expect.objectContaining({
302
- text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678'
296
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
303
297
  })
304
298
  ])
305
299
  }
@@ -310,7 +304,7 @@ describe('NationalGridFieldNumberField', () => {
310
304
  value: getFormData('N123456789'),
311
305
  errors: expect.arrayContaining([
312
306
  expect.objectContaining({
313
- text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678'
307
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
314
308
  })
315
309
  ])
316
310
  }
@@ -321,7 +315,7 @@ describe('NationalGridFieldNumberField', () => {
321
315
  value: getFormData('NGABCDEFGH'),
322
316
  errors: expect.arrayContaining([
323
317
  expect.objectContaining({
324
- text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678'
318
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
325
319
  })
326
320
  ])
327
321
  }
@@ -332,6 +326,7 @@ describe('NationalGridFieldNumberField', () => {
332
326
  description: 'Custom validation message',
333
327
  component: {
334
328
  title: 'Example National Grid field number',
329
+ shortDescription: 'Grid field',
335
330
  name: 'myComponent',
336
331
  type: ComponentType.NationalGridFieldNumberField,
337
332
  options: {
@@ -367,6 +362,7 @@ describe('NationalGridFieldNumberField', () => {
367
362
  description: 'Custom validation messages (multiple)',
368
363
  component: {
369
364
  title: 'Example National Grid field number',
365
+ shortDescription: 'Grid field',
370
366
  name: 'myComponent',
371
367
  type: ComponentType.NationalGridFieldNumberField,
372
368
  options: {
@@ -417,6 +413,7 @@ describe('NationalGridFieldNumberField', () => {
417
413
  description: 'Optional field',
418
414
  component: {
419
415
  title: 'Example National Grid field number',
416
+ shortDescription: 'Grid field',
420
417
  name: 'myComponent',
421
418
  type: ComponentType.NationalGridFieldNumberField,
422
419
  options: {
@@ -1,4 +1,5 @@
1
1
  import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
2
+ import lowerFirst from 'lodash/lowerFirst.js'
2
3
 
3
4
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4
5
 
@@ -6,16 +7,15 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
6
7
  declare options: NationalGridFieldNumberFieldComponent['options']
7
8
 
8
9
  protected getValidationConfig() {
9
- // Regex for OS grid references and parcel IDs
10
+ // Regex for OS national grid field references (NGFR)
10
11
  // 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
12
+ // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789
13
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}))$/
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})$/
15
15
 
16
16
  return {
17
17
  pattern,
18
- patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`
18
+ patternErrorMessage: `Enter a valid National Grid field number for ${lowerFirst(this.label)} like NG 1234 5678`
19
19
  }
20
20
  }
21
21
 
@@ -100,12 +100,22 @@ describe('OsGridRefField', () => {
100
100
  const result1 = collection.validate(getFormData('SD865005'))
101
101
  const result2 = collection.validate(getFormData('SD 865 005'))
102
102
 
103
+ const result3 = collection.validate(getFormData('SD86565005'))
104
+ const result4 = collection.validate(getFormData('SD 8656 0065'))
105
+
106
+ const result5 = collection.validate(getFormData('SD8654454005'))
107
+ const result6 = collection.validate(getFormData('SD 86455 00545'))
108
+
103
109
  // Test case-insensitive
104
- const result3 = collection.validate(getFormData('nt123456'))
110
+ const result7 = collection.validate(getFormData('nt123456'))
105
111
 
106
112
  expect(result1.errors).toBeUndefined()
107
113
  expect(result2.errors).toBeUndefined()
108
114
  expect(result3.errors).toBeUndefined()
115
+ expect(result4.errors).toBeUndefined()
116
+ expect(result5.errors).toBeUndefined()
117
+ expect(result6.errors).toBeUndefined()
118
+ expect(result7.errors).toBeUndefined()
109
119
  })
110
120
 
111
121
  it('retains values with spaces per GDS guidance', () => {
@@ -137,8 +147,9 @@ describe('OsGridRefField', () => {
137
147
  const result4 = collection.validate(getFormData('TQ1234567')) // Wrong number of digits (7)
138
148
 
139
149
  // Test mismatched digit counts (must be either 3+3, 4+4 or 5+5, not mixed)
140
- const result5 = collection.validate(getFormData('SN 4444 55555')) // mismatched digit counts
141
- const result6 = collection.validate(getFormData('SN 55555 4444')) // mismatched digit counts
150
+ const result5 = collection.validate(getFormData('SN 333 4444')) // mismatched digit counts
151
+ const result6 = collection.validate(getFormData('SN 4444 55555')) // mismatched digit counts
152
+ const result7 = collection.validate(getFormData('SN 55555 4444')) // mismatched digit counts
142
153
 
143
154
  expect(result1.errors).toBeTruthy()
144
155
  expect(result2.errors).toBeTruthy()
@@ -146,6 +157,7 @@ describe('OsGridRefField', () => {
146
157
  expect(result4.errors).toBeTruthy()
147
158
  expect(result5.errors).toBeTruthy()
148
159
  expect(result6.errors).toBeTruthy()
160
+ expect(result7.errors).toBeTruthy()
149
161
  })
150
162
  })
151
163
 
@@ -284,6 +296,7 @@ describe('OsGridRefField', () => {
284
296
  description: 'Pattern validation',
285
297
  component: {
286
298
  title: 'Example OS grid reference',
299
+ shortDescription: 'Grid reference',
287
300
  name: 'myComponent',
288
301
  type: ComponentType.OsGridRefField,
289
302
  options: {}
@@ -295,7 +308,7 @@ describe('OsGridRefField', () => {
295
308
  value: getFormData('TQ12345'),
296
309
  errors: expect.arrayContaining([
297
310
  expect.objectContaining({
298
- text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
311
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
299
312
  })
300
313
  ])
301
314
  }
@@ -306,7 +319,7 @@ describe('OsGridRefField', () => {
306
319
  value: getFormData('AA1234567'),
307
320
  errors: expect.arrayContaining([
308
321
  expect.objectContaining({
309
- text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
322
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
310
323
  })
311
324
  ])
312
325
  }
@@ -317,7 +330,7 @@ describe('OsGridRefField', () => {
317
330
  value: getFormData('TQABCDEF'),
318
331
  errors: expect.arrayContaining([
319
332
  expect.objectContaining({
320
- text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
333
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
321
334
  })
322
335
  ])
323
336
  }
@@ -1,4 +1,5 @@
1
1
  import { type OsGridRefFieldComponent } from '@defra/forms-model'
2
+ import lowerFirst from 'lodash/lowerFirst.js'
2
3
 
3
4
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4
5
 
@@ -6,15 +7,18 @@ export class OsGridRefField extends LocationFieldBase {
6
7
  declare options: OsGridRefFieldComponent['options']
7
8
 
8
9
  protected getValidationConfig() {
9
- // Regex for OS grid references and parcel IDs
10
+ // Regex for OS national grid references (NGR)
10
11
  // Validates specific valid OS grid letter combinations with:
11
- // - 2 letters & 6 digits (e.g., SD865005 or SD 865 005)
12
+ // - 2 letters & 6 digits in 2 blocks of 3 e.g. ST 678 678
13
+ // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789
14
+ // - 2 letters & 10 digits in 2 blocks of 5 e.g. SO 12345 12345
15
+ // Optional spaces between each block
12
16
  const pattern =
13
- /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3}))$/
17
+ /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3})|([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/
14
18
 
15
19
  return {
16
20
  pattern,
17
- patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`
21
+ patternErrorMessage: `Enter a valid OS grid reference for ${lowerFirst(this.label)} like TQ123456`
18
22
  }
19
23
  }
20
24
 
@@ -64,6 +64,9 @@ export interface DateInputItem {
64
64
  // but not by date fields. This interface is reused by both component types.
65
65
  prefix?: ComponentText
66
66
  suffix?: ComponentText
67
+ errorMessage?: {
68
+ text: string
69
+ }
67
70
  condition?: undefined
68
71
  }
69
72
 
@@ -24,7 +24,7 @@ import {
24
24
  type Page
25
25
  } from '@defra/forms-model'
26
26
  import { add, format } from 'date-fns'
27
- import { Parser, type Value } from 'expr-eval'
27
+ import { Parser, type Value } from 'expr-eval-fork'
28
28
  import joi from 'joi'
29
29
 
30
30
  import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
@@ -3,7 +3,7 @@ import {
3
3
  type FormComponentsDef,
4
4
  type Section
5
5
  } from '@defra/forms-model'
6
- import { type Expression } from 'expr-eval'
6
+ import { type Expression } from 'expr-eval-fork'
7
7
 
8
8
  import {
9
9
  getAnswer,
@@ -12,22 +12,22 @@
12
12
  }) }}
13
13
  {% endif %}
14
14
 
15
- <div class="app-location-input">
15
+ <div class="govuk-grid-row">
16
16
  {% for item in component.model.items %}
17
- <div class="app-location-input__item">
17
+ <div class="govuk-grid-column-one-third">
18
18
  {{ govukInput({
19
19
  id: item.id,
20
20
  name: item.name,
21
21
  label: {
22
- text: item.label,
23
- classes: "govuk-label--s"
22
+ text: item.label
24
23
  },
25
24
  classes: item.classes,
26
25
  value: item.value,
27
26
  type: inputType,
28
27
  inputmode: inputMode,
29
28
  prefix: item.prefix,
30
- suffix: item.suffix
29
+ suffix: item.suffix,
30
+ errorMessage: item.errorMessage
31
31
  }) }}
32
32
  </div>
33
33
  {% endfor %}
@@ -41,13 +41,21 @@
41
41
  {% endif %}
42
42
  {% endset %}
43
43
 
44
- {{ govukFieldset({
45
- legend: {
46
- text: component.model.fieldset.legend.text,
47
- classes: component.model.fieldset.legend.classes,
48
- isPageHeading: false
49
- },
50
- html: fieldsetHtml
51
- }) }}
52
- {% endmacro %}
44
+ {% set hasErrors = false %}
45
+ {% for item in component.model.items %}
46
+ {% if item.errorMessage %}
47
+ {% set hasErrors = true %}
48
+ {% endif %}
49
+ {% endfor %}
53
50
 
51
+ <div class="govuk-form-group {{ "govuk-form-group--error" if (hasErrors or component.model.errors) }}">
52
+ {{ govukFieldset({
53
+ legend: {
54
+ text: component.model.fieldset.legend.text,
55
+ classes: component.model.fieldset.legend.classes,
56
+ isPageHeading: false
57
+ },
58
+ html: fieldsetHtml
59
+ }) }}
60
+ </div>
61
+ {% endmacro %}
@@ -1,60 +0,0 @@
1
- @use "govuk-frontend" as *;
2
-
3
- .app-location-input {
4
- @include govuk-clearfix;
5
- font-size: 0; // removes whitespace caused by inline-block
6
- margin-bottom: govuk-spacing(6);
7
-
8
- &:has(.govuk-input--error) {
9
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
10
- padding-left: govuk-spacing(3);
11
- margin-top: 0;
12
- }
13
- }
14
-
15
- .govuk-hint:has(+ .app-location-input .govuk-input--error) {
16
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
17
- padding-left: govuk-spacing(3);
18
- margin-bottom: 0;
19
- }
20
-
21
- .govuk-fieldset:has(.app-location-input .govuk-input--error) {
22
- .govuk-fieldset__legend {
23
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
24
- padding-left: govuk-spacing(3);
25
- margin-bottom: 0;
26
- }
27
-
28
- .govuk-fieldset__legend + .govuk-hint {
29
- margin-top: 0;
30
- }
31
- }
32
-
33
- .app-location-input__item {
34
- display: inline-block;
35
- margin-right: govuk-spacing(4);
36
- margin-bottom: govuk-spacing(4);
37
-
38
- &:last-child {
39
- margin-right: 0;
40
- }
41
-
42
- @include govuk-media-query($from: tablet) {
43
- margin-bottom: 0;
44
- }
45
-
46
- .govuk-form-group {
47
- margin-bottom: 0;
48
- display: inline-block;
49
- width: auto;
50
- }
51
-
52
- .govuk-label {
53
- display: block;
54
- }
55
-
56
- .govuk-input {
57
- margin-bottom: 0;
58
- width: auto;
59
- }
60
- }
@@ -1,60 +0,0 @@
1
- @use "govuk-frontend" as *;
2
-
3
- .app-location-input {
4
- @include govuk-clearfix;
5
- font-size: 0; // removes whitespace caused by inline-block
6
- margin-bottom: govuk-spacing(6);
7
-
8
- &:has(.govuk-input--error) {
9
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
10
- padding-left: govuk-spacing(3);
11
- margin-top: 0;
12
- }
13
- }
14
-
15
- .govuk-hint:has(+ .app-location-input .govuk-input--error) {
16
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
17
- padding-left: govuk-spacing(3);
18
- margin-bottom: 0;
19
- }
20
-
21
- .govuk-fieldset:has(.app-location-input .govuk-input--error) {
22
- .govuk-fieldset__legend {
23
- border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
24
- padding-left: govuk-spacing(3);
25
- margin-bottom: 0;
26
- }
27
-
28
- .govuk-fieldset__legend + .govuk-hint {
29
- margin-top: 0;
30
- }
31
- }
32
-
33
- .app-location-input__item {
34
- display: inline-block;
35
- margin-right: govuk-spacing(4);
36
- margin-bottom: govuk-spacing(4);
37
-
38
- &:last-child {
39
- margin-right: 0;
40
- }
41
-
42
- @include govuk-media-query($from: tablet) {
43
- margin-bottom: 0;
44
- }
45
-
46
- .govuk-form-group {
47
- margin-bottom: 0;
48
- display: inline-block;
49
- width: auto;
50
- }
51
-
52
- .govuk-label {
53
- display: block;
54
- }
55
-
56
- .govuk-input {
57
- margin-bottom: 0;
58
- width: auto;
59
- }
60
- }