@defra/forms-engine-plugin 4.0.5 → 4.0.7
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.
- package/.public/stylesheets/application.min.css +2 -2
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/_location-input.scss +60 -0
- package/.server/client/stylesheets/application.scss +1 -6
- package/.server/client/stylesheets/shared.scss +8 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
- package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -1
- package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
- package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
- package/.server/server/plugins/engine/components/LatLongField.js +164 -0
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
- package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
- package/.server/server/plugins/engine/components/helpers/components.js +21 -29
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +4 -0
- package/.server/server/plugins/engine/components/index.js +4 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
- package/.server/server/plugins/engine/components/markdownParser.js +28 -0
- package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
- package/.server/server/plugins/engine/components/types.d.ts +10 -0
- package/.server/server/plugins/engine/components/types.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +1 -1
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +2 -2
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/package.json +3 -3
- package/src/client/stylesheets/_location-input.scss +60 -0
- package/src/client/stylesheets/application.scss +1 -6
- package/src/client/stylesheets/shared.scss +8 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +1 -1
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
- package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
- package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
- package/src/server/plugins/engine/components/LatLongField.ts +213 -0
- package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
- package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
- package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
- package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
- package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
- package/src/server/plugins/engine/components/helpers/components.ts +39 -47
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
- package/src/server/plugins/engine/components/index.ts +4 -0
- package/src/server/plugins/engine/components/markdownParser.ts +40 -0
- package/src/server/plugins/engine/components/types.ts +14 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
- package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
- package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
- package/src/server/plugins/engine/types/index.ts +2 -0
- package/src/server/plugins/engine/types.ts +4 -0
- package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { ComponentType, type OsGridRefFieldComponent } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
4
|
+
import { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
|
|
5
|
+
import {
|
|
6
|
+
getAnswer,
|
|
7
|
+
type Field
|
|
8
|
+
} from '~/src/server/plugins/engine/components/helpers/components.js'
|
|
9
|
+
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
10
|
+
import definition from '~/test/form/definitions/blank.js'
|
|
11
|
+
import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
|
|
12
|
+
|
|
13
|
+
describe('OsGridRefField', () => {
|
|
14
|
+
let model: FormModel
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
model = new FormModel(definition, {
|
|
18
|
+
basePath: 'test'
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('Defaults', () => {
|
|
23
|
+
let def: OsGridRefFieldComponent
|
|
24
|
+
let collection: ComponentCollection
|
|
25
|
+
let field: Field
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
def = {
|
|
29
|
+
title: 'Example OS grid reference',
|
|
30
|
+
name: 'myComponent',
|
|
31
|
+
type: ComponentType.OsGridRefField,
|
|
32
|
+
options: {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
collection = new ComponentCollection([def], { model })
|
|
36
|
+
field = collection.fields[0]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('Schema', () => {
|
|
40
|
+
it('uses component title as label as default', () => {
|
|
41
|
+
const { formSchema } = collection
|
|
42
|
+
const { keys } = formSchema.describe()
|
|
43
|
+
|
|
44
|
+
expect(keys).toHaveProperty(
|
|
45
|
+
'myComponent',
|
|
46
|
+
expect.objectContaining({
|
|
47
|
+
flags: expect.objectContaining({
|
|
48
|
+
label: 'Example OS grid reference'
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('uses component name as keys', () => {
|
|
55
|
+
const { formSchema } = collection
|
|
56
|
+
const { keys } = formSchema.describe()
|
|
57
|
+
|
|
58
|
+
expect(field.keys).toEqual(['myComponent'])
|
|
59
|
+
expect(field.collection).toBeUndefined()
|
|
60
|
+
|
|
61
|
+
for (const key of field.keys) {
|
|
62
|
+
expect(keys).toHaveProperty(key)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('is required by default', () => {
|
|
67
|
+
const { formSchema } = collection
|
|
68
|
+
const { keys } = formSchema.describe()
|
|
69
|
+
|
|
70
|
+
expect(keys).toHaveProperty(
|
|
71
|
+
'myComponent',
|
|
72
|
+
expect.objectContaining({
|
|
73
|
+
flags: expect.objectContaining({
|
|
74
|
+
presence: 'required'
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('is optional when configured', () => {
|
|
81
|
+
const collectionOptional = new ComponentCollection(
|
|
82
|
+
[{ ...def, options: { required: false } }],
|
|
83
|
+
{ model }
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const { formSchema } = collectionOptional
|
|
87
|
+
const { keys } = formSchema.describe()
|
|
88
|
+
|
|
89
|
+
expect(keys).toHaveProperty(
|
|
90
|
+
'myComponent',
|
|
91
|
+
expect.objectContaining({ allow: [''] })
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const result = collectionOptional.validate(getFormData(''))
|
|
95
|
+
expect(result.errors).toBeUndefined()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('accepts valid values', () => {
|
|
99
|
+
// Test 6-digit format (common OS grid reference)
|
|
100
|
+
const result1 = collection.validate(getFormData('SD865005'))
|
|
101
|
+
const result2 = collection.validate(getFormData('SD 865 005'))
|
|
102
|
+
|
|
103
|
+
// Test 8-digit parcel ID format (2x4)
|
|
104
|
+
const result3 = collection.validate(getFormData('TQ12345678'))
|
|
105
|
+
const result4 = collection.validate(getFormData('TQ 1234 5678'))
|
|
106
|
+
|
|
107
|
+
// Test 10-digit OS grid reference format (2x5)
|
|
108
|
+
const result5 = collection.validate(getFormData('SU1234567890'))
|
|
109
|
+
const result6 = collection.validate(getFormData('SU 12345 67890'))
|
|
110
|
+
|
|
111
|
+
// Test case-insensitive
|
|
112
|
+
const result7 = collection.validate(getFormData('nt12345678'))
|
|
113
|
+
|
|
114
|
+
// Test various valid OS grid formats
|
|
115
|
+
const result8 = collection.validate(getFormData('SN 1232 1223')) // parcel ID format
|
|
116
|
+
const result9 = collection.validate(getFormData('SN 12324 12234')) // OS grid ref format
|
|
117
|
+
const result10 = collection.validate(getFormData('ST 6789 6789')) // parcel ID with different letters
|
|
118
|
+
const result11 = collection.validate(getFormData('SO 12345 12345')) // OS grid ref with different letters
|
|
119
|
+
|
|
120
|
+
expect(result1.errors).toBeUndefined()
|
|
121
|
+
expect(result2.errors).toBeUndefined()
|
|
122
|
+
expect(result3.errors).toBeUndefined()
|
|
123
|
+
expect(result4.errors).toBeUndefined()
|
|
124
|
+
expect(result5.errors).toBeUndefined()
|
|
125
|
+
expect(result6.errors).toBeUndefined()
|
|
126
|
+
expect(result7.errors).toBeUndefined()
|
|
127
|
+
expect(result8.errors).toBeUndefined()
|
|
128
|
+
expect(result9.errors).toBeUndefined()
|
|
129
|
+
expect(result10.errors).toBeUndefined()
|
|
130
|
+
expect(result11.errors).toBeUndefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('formats values with spaces per GDS guidance', () => {
|
|
134
|
+
const result1 = collection.validate(getFormData('SD865005'))
|
|
135
|
+
const result2 = collection.validate(getFormData('TQ 1234 5678'))
|
|
136
|
+
const result3 = collection.validate(getFormData('SU1234567890'))
|
|
137
|
+
const result4 = collection.validate(getFormData('TQ12345678'))
|
|
138
|
+
|
|
139
|
+
expect(result1.value.myComponent).toBe('SD 865 005')
|
|
140
|
+
expect(result2.value.myComponent).toBe('TQ 1234 5678')
|
|
141
|
+
expect(result3.value.myComponent).toBe('SU 12345 67890')
|
|
142
|
+
expect(result4.value.myComponent).toBe('TQ 1234 5678')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('adds errors for empty value', () => {
|
|
146
|
+
const result = collection.validate(getFormData(''))
|
|
147
|
+
|
|
148
|
+
expect(result.errors).toEqual([
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
text: 'Enter example OS grid reference'
|
|
151
|
+
})
|
|
152
|
+
])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('adds errors for invalid values', () => {
|
|
156
|
+
const result1 = collection.validate(getFormData('INVALID'))
|
|
157
|
+
const result2 = collection.validate(getFormData('TQ12345')) // Wrong number of digits (5)
|
|
158
|
+
const result3 = collection.validate(getFormData('AA12345678')) // Invalid letter combination
|
|
159
|
+
const result4 = collection.validate(getFormData('TQ1234567')) // Wrong number of digits (7)
|
|
160
|
+
|
|
161
|
+
// Test mismatched digit counts (must be either 3+3, 4+4 or 5+5, not mixed)
|
|
162
|
+
const result5 = collection.validate(getFormData('SN 4444 55555')) // mismatched digit counts
|
|
163
|
+
const result6 = collection.validate(getFormData('SN 55555 4444')) // mismatched digit counts
|
|
164
|
+
|
|
165
|
+
expect(result1.errors).toBeTruthy()
|
|
166
|
+
expect(result2.errors).toBeTruthy()
|
|
167
|
+
expect(result3.errors).toBeTruthy()
|
|
168
|
+
expect(result4.errors).toBeTruthy()
|
|
169
|
+
expect(result5.errors).toBeTruthy()
|
|
170
|
+
expect(result6.errors).toBeTruthy()
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('State', () => {
|
|
175
|
+
it('returns text from state', () => {
|
|
176
|
+
const state1 = getFormState('TQ12345678')
|
|
177
|
+
const state2 = getFormState(null)
|
|
178
|
+
|
|
179
|
+
const answer1 = getAnswer(field, state1)
|
|
180
|
+
const answer2 = getAnswer(field, state2)
|
|
181
|
+
|
|
182
|
+
expect(answer1).toBe('TQ12345678')
|
|
183
|
+
expect(answer2).toBe('')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('returns payload from state', () => {
|
|
187
|
+
const state1 = getFormState('TQ12345678')
|
|
188
|
+
const state2 = getFormState(null)
|
|
189
|
+
|
|
190
|
+
const payload1 = field.getFormDataFromState(state1)
|
|
191
|
+
const payload2 = field.getFormDataFromState(state2)
|
|
192
|
+
|
|
193
|
+
expect(payload1).toEqual(getFormData('TQ12345678'))
|
|
194
|
+
expect(payload2).toEqual(getFormData())
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('returns value from state', () => {
|
|
198
|
+
const state1 = getFormState('TQ12345678')
|
|
199
|
+
const state2 = getFormState(null)
|
|
200
|
+
|
|
201
|
+
const value1 = field.getFormValueFromState(state1)
|
|
202
|
+
const value2 = field.getFormValueFromState(state2)
|
|
203
|
+
|
|
204
|
+
expect(value1).toBe('TQ12345678')
|
|
205
|
+
expect(value2).toBeUndefined()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('returns context for conditions and form submission', () => {
|
|
209
|
+
const state1 = getFormState('TQ12345678')
|
|
210
|
+
const state2 = getFormState(null)
|
|
211
|
+
|
|
212
|
+
const value1 = field.getContextValueFromState(state1)
|
|
213
|
+
const value2 = field.getContextValueFromState(state2)
|
|
214
|
+
|
|
215
|
+
expect(value1).toBe('TQ12345678')
|
|
216
|
+
expect(value2).toBeNull()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('returns state from payload', () => {
|
|
220
|
+
const payload1 = getFormData('TQ12345678')
|
|
221
|
+
const payload2 = getFormData()
|
|
222
|
+
|
|
223
|
+
const value1 = field.getStateFromValidForm(payload1)
|
|
224
|
+
const value2 = field.getStateFromValidForm(payload2)
|
|
225
|
+
|
|
226
|
+
expect(value1).toEqual(getFormState('TQ12345678'))
|
|
227
|
+
expect(value2).toEqual(getFormState(null))
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('View model', () => {
|
|
232
|
+
it('sets Nunjucks component defaults', () => {
|
|
233
|
+
const viewModel = field.getViewModel(getFormData('TQ12345678'))
|
|
234
|
+
|
|
235
|
+
expect(viewModel).toEqual(
|
|
236
|
+
expect.objectContaining({
|
|
237
|
+
label: { text: def.title },
|
|
238
|
+
name: 'myComponent',
|
|
239
|
+
id: 'myComponent',
|
|
240
|
+
value: 'TQ12345678'
|
|
241
|
+
})
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('includes instruction text when provided', () => {
|
|
246
|
+
const componentWithInstruction = new OsGridRefField(
|
|
247
|
+
{
|
|
248
|
+
...def,
|
|
249
|
+
options: { instructionText: 'Enter in format **TQ12345678**' }
|
|
250
|
+
},
|
|
251
|
+
{ model }
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
const viewModel = componentWithInstruction.getViewModel(
|
|
255
|
+
getFormData('TQ12345678')
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
const instructionText =
|
|
259
|
+
'instructionText' in viewModel ? viewModel.instructionText : undefined
|
|
260
|
+
expect(instructionText).toBeTruthy()
|
|
261
|
+
expect(instructionText).toContain('TQ12345678')
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('AllPossibleErrors', () => {
|
|
266
|
+
it('should return errors from instance method', () => {
|
|
267
|
+
const errors = field.getAllPossibleErrors()
|
|
268
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
269
|
+
expect(errors.advancedSettingsErrors).toEqual([])
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should return errors from static method', () => {
|
|
273
|
+
const staticErrors = OsGridRefField.getAllPossibleErrors()
|
|
274
|
+
expect(staticErrors.baseErrors).not.toBeEmpty()
|
|
275
|
+
expect(staticErrors.advancedSettingsErrors).toEqual([])
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('Validation', () => {
|
|
281
|
+
describe.each([
|
|
282
|
+
{
|
|
283
|
+
description: 'Trim empty spaces',
|
|
284
|
+
component: {
|
|
285
|
+
title: 'Example OS grid reference',
|
|
286
|
+
name: 'myComponent',
|
|
287
|
+
type: ComponentType.OsGridRefField,
|
|
288
|
+
options: {}
|
|
289
|
+
},
|
|
290
|
+
assertions: [
|
|
291
|
+
{
|
|
292
|
+
input: getFormData(' TQ12345678'),
|
|
293
|
+
output: { value: getFormData('TQ 1234 5678') }
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
input: getFormData('TQ12345678 '),
|
|
297
|
+
output: { value: getFormData('TQ 1234 5678') }
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
input: getFormData(' TQ12345678 \n\n'),
|
|
301
|
+
output: { value: getFormData('TQ 1234 5678') }
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
description: 'Pattern validation',
|
|
307
|
+
component: {
|
|
308
|
+
title: 'Example OS grid reference',
|
|
309
|
+
name: 'myComponent',
|
|
310
|
+
type: ComponentType.OsGridRefField,
|
|
311
|
+
options: {}
|
|
312
|
+
},
|
|
313
|
+
assertions: [
|
|
314
|
+
{
|
|
315
|
+
input: getFormData('TQ12345'),
|
|
316
|
+
output: {
|
|
317
|
+
value: getFormData('TQ12345'),
|
|
318
|
+
errors: expect.arrayContaining([
|
|
319
|
+
expect.objectContaining({
|
|
320
|
+
text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
|
|
321
|
+
})
|
|
322
|
+
])
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
input: getFormData('AA1234567'),
|
|
327
|
+
output: {
|
|
328
|
+
value: getFormData('AA1234567'),
|
|
329
|
+
errors: expect.arrayContaining([
|
|
330
|
+
expect.objectContaining({
|
|
331
|
+
text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
|
|
332
|
+
})
|
|
333
|
+
])
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
input: getFormData('TQABCDEF'),
|
|
338
|
+
output: {
|
|
339
|
+
value: getFormData('TQABCDEF'),
|
|
340
|
+
errors: expect.arrayContaining([
|
|
341
|
+
expect.objectContaining({
|
|
342
|
+
text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456'
|
|
343
|
+
})
|
|
344
|
+
])
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
description: 'Custom validation message',
|
|
351
|
+
component: {
|
|
352
|
+
title: 'Example OS grid reference',
|
|
353
|
+
name: 'myComponent',
|
|
354
|
+
type: ComponentType.OsGridRefField,
|
|
355
|
+
options: {
|
|
356
|
+
customValidationMessage: 'This is a custom error'
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
assertions: [
|
|
360
|
+
{
|
|
361
|
+
input: getFormData(''),
|
|
362
|
+
output: {
|
|
363
|
+
value: getFormData(''),
|
|
364
|
+
errors: [
|
|
365
|
+
expect.objectContaining({
|
|
366
|
+
text: 'This is a custom error'
|
|
367
|
+
})
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
input: getFormData('INVALID'),
|
|
373
|
+
output: {
|
|
374
|
+
value: getFormData('INVALID'),
|
|
375
|
+
errors: expect.arrayContaining([
|
|
376
|
+
expect.objectContaining({
|
|
377
|
+
text: 'This is a custom error'
|
|
378
|
+
})
|
|
379
|
+
])
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
description: 'Custom validation messages (multiple)',
|
|
386
|
+
component: {
|
|
387
|
+
title: 'Example OS grid reference',
|
|
388
|
+
name: 'myComponent',
|
|
389
|
+
type: ComponentType.OsGridRefField,
|
|
390
|
+
options: {
|
|
391
|
+
customValidationMessages: {
|
|
392
|
+
'any.required': 'This is a custom required error',
|
|
393
|
+
'string.empty': 'This is a custom empty string error',
|
|
394
|
+
'string.pattern.base': 'This is a custom pattern error'
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
assertions: [
|
|
399
|
+
{
|
|
400
|
+
input: getFormData(),
|
|
401
|
+
output: {
|
|
402
|
+
value: getFormData(''),
|
|
403
|
+
errors: [
|
|
404
|
+
expect.objectContaining({
|
|
405
|
+
text: 'This is a custom required error'
|
|
406
|
+
})
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
input: getFormData(''),
|
|
412
|
+
output: {
|
|
413
|
+
value: getFormData(''),
|
|
414
|
+
errors: [
|
|
415
|
+
expect.objectContaining({
|
|
416
|
+
text: 'This is a custom empty string error'
|
|
417
|
+
})
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
input: getFormData('INVALID'),
|
|
423
|
+
output: {
|
|
424
|
+
value: getFormData('INVALID'),
|
|
425
|
+
errors: expect.arrayContaining([
|
|
426
|
+
expect.objectContaining({
|
|
427
|
+
text: 'This is a custom pattern error'
|
|
428
|
+
})
|
|
429
|
+
])
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
description: 'Optional field',
|
|
436
|
+
component: {
|
|
437
|
+
title: 'Example OS grid reference',
|
|
438
|
+
name: 'myComponent',
|
|
439
|
+
type: ComponentType.OsGridRefField,
|
|
440
|
+
options: {
|
|
441
|
+
required: false
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
assertions: [
|
|
445
|
+
{
|
|
446
|
+
input: getFormData(''),
|
|
447
|
+
output: { value: getFormData('') }
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
])('$description', ({ component: def, assertions }) => {
|
|
452
|
+
let collection: ComponentCollection
|
|
453
|
+
|
|
454
|
+
beforeEach(() => {
|
|
455
|
+
collection = new ComponentCollection([def as OsGridRefFieldComponent], {
|
|
456
|
+
model
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
it.each([...assertions])(
|
|
461
|
+
'validates custom example',
|
|
462
|
+
({ input, output }) => {
|
|
463
|
+
const result = collection.validate(input)
|
|
464
|
+
expect(result).toEqual(output)
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type OsGridRefFieldComponent } from '@defra/forms-model'
|
|
2
|
+
import type joi from 'joi'
|
|
3
|
+
|
|
4
|
+
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
|
|
5
|
+
|
|
6
|
+
export class OsGridRefField extends LocationFieldBase {
|
|
7
|
+
declare options: OsGridRefFieldComponent['options']
|
|
8
|
+
|
|
9
|
+
protected getValidationConfig() {
|
|
10
|
+
// Regex for OS grid references and parcel IDs
|
|
11
|
+
// Validates specific valid OS grid letter combinations with:
|
|
12
|
+
// - 6 digits (e.g., SD865005 or SD 865 005)
|
|
13
|
+
// - 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789
|
|
14
|
+
// - 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345
|
|
15
|
+
const osGridPattern =
|
|
16
|
+
/^(?:[sn][a-hj-z]|[to][abfglmqrvw]|h[l-z]|j[lmqrvw])\s?(?:\d{3}\s?\d{3}|\d{4}\s?\d{4}|\d{5}\s?\d{5})$/i
|
|
17
|
+
|
|
18
|
+
// More permissive pattern for initial validation (allows spaces to be cleaned)
|
|
19
|
+
const initialPattern = /^[A-Za-z]{2}[\d\s]*$/
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
pattern: initialPattern,
|
|
23
|
+
patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`,
|
|
24
|
+
customValidation: (value: string, helpers: joi.CustomHelpers) => {
|
|
25
|
+
// Strip spaces from the input for processing
|
|
26
|
+
const cleanValue = value.replace(/\s/g, '')
|
|
27
|
+
const letters = cleanValue.substring(0, 2)
|
|
28
|
+
const numbers = cleanValue.substring(2)
|
|
29
|
+
|
|
30
|
+
// Validate number length
|
|
31
|
+
if (
|
|
32
|
+
numbers.length !== 6 &&
|
|
33
|
+
numbers.length !== 8 &&
|
|
34
|
+
numbers.length !== 10
|
|
35
|
+
) {
|
|
36
|
+
return helpers.error('string.pattern.base')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Format with spaces: XX 123 456, XX 1234 5678, or XX 12345 67890
|
|
40
|
+
const halfLength = numbers.length / 2
|
|
41
|
+
const formattedValue = `${letters} ${numbers.substring(0, halfLength)} ${numbers.substring(halfLength)}`
|
|
42
|
+
|
|
43
|
+
// Validate the formatted value against the OS grid pattern
|
|
44
|
+
if (!osGridPattern.test(formattedValue)) {
|
|
45
|
+
return helpers.error('string.pattern.base')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return formatted value with spaces per GDS guidance
|
|
49
|
+
return formattedValue
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected getErrorTemplates() {
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
type: 'pattern',
|
|
58
|
+
template:
|
|
59
|
+
'Enter a valid OS grid reference for [short description] like TQ123456'
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
66
|
+
*/
|
|
67
|
+
static getAllPossibleErrors() {
|
|
68
|
+
const instance = Object.create(OsGridRefField.prototype) as OsGridRefField
|
|
69
|
+
return instance.getAllPossibleErrors()
|
|
70
|
+
}
|
|
71
|
+
}
|