Skip to content

Commit 54528fa

Browse files
Feat/df 529 change lat long 2 (#239)
* Fix unicorn form * Revert changes to NumberField * Remove minLength and maxLength from EN field * Remove minPrecision, minLength and maxLength from LL field * Update NGR and OS ref fields * Remove case insensitive flag
1 parent 5976188 commit 54528fa

12 files changed

Lines changed: 92 additions & 827 deletions

src/server/forms/register-as-a-unicorn-breeder.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ pages:
203203
path: '/how-many-members-of-staff-will-look-after-the-unicorns'
204204
section: susaYr
205205
next:
206-
- path: '/summary'
206+
- path: '/declaration'
207207
components:
208208
- name: zhJMaM
209209
options:
@@ -219,7 +219,7 @@ pages:
219219
controller: FileUploadPageController
220220
section: Regnsa
221221
next:
222-
- path: '/declaration'
222+
- path: '/how-many-unicorns-do-you-expect-to-breed-each-year'
223223
components:
224224
- name: dLzALM
225225
title: Documents

src/server/plugins/engine/components/EastingNorthingField.test.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -556,14 +556,7 @@ describe('EastingNorthingField', () => {
556556
easting: 12345.5,
557557
northing: 1234567
558558
}),
559-
// Two errors expected: decimal input triggers both integer validation
560-
// and length validation ('12345.5' is 7 chars, max is 6)
561559
errors: [
562-
expect.objectContaining({
563-
text: expect.stringMatching(
564-
/Easting for .* must be between 1 and 6 digits/
565-
)
566-
}),
567560
expect.objectContaining({
568561
text: expect.stringMatching(
569562
/Easting for .* must be between 1 and 6 digits/
@@ -582,14 +575,7 @@ describe('EastingNorthingField', () => {
582575
easting: 12345,
583576
northing: 1234567.5
584577
}),
585-
// Two errors expected: decimal input triggers both integer validation
586-
// and length validation ('1234567.5' is 9 chars, max is 7)
587578
errors: [
588-
expect.objectContaining({
589-
text: expect.stringMatching(
590-
/Northing for .* must be between 1 and 7 digits/
591-
)
592-
}),
593579
expect.objectContaining({
594580
text: expect.stringMatching(
595581
/Northing for .* must be between 1 and 7 digits/

src/server/plugins/engine/components/EastingNorthingField.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,6 @@ const DEFAULT_EASTING_MAX = 700000
3232
const DEFAULT_NORTHING_MIN = 0
3333
const DEFAULT_NORTHING_MAX = 1300000
3434

35-
// Easting length constraints (integer values only, no decimals)
36-
// Min: 1 char for values like "0" or single digit values
37-
// Max: 6 chars for values up to 700000 (British National Grid easting limit)
38-
const EASTING_MIN_LENGTH = 1
39-
const EASTING_MAX_LENGTH = 6
40-
41-
// Northing length constraints (integer values only, no decimals)
42-
// Min: 1 char for values like "0" or single digit values
43-
// Max: 7 chars for values up to 1300000 (British National Grid northing limit)
44-
const NORTHING_MIN_LENGTH = 1
45-
const NORTHING_MAX_LENGTH = 7
46-
4735
export class EastingNorthingField extends FormComponent {
4836
declare options: EastingNorthingFieldComponent['options']
4937
declare formSchema: ObjectSchema<FormPayload>
@@ -73,9 +61,7 @@ export class EastingNorthingField extends FormComponent {
7361
'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`,
7462
'number.precision': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
7563
'number.integer': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
76-
'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
77-
'number.minLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
78-
'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`
64+
'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`
7965
})
8066

8167
const northingValidationMessages: LanguageMessages =
@@ -86,9 +72,7 @@ export class EastingNorthingField extends FormComponent {
8672
'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`,
8773
'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
8874
'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
89-
'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
90-
'number.minLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
91-
'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`
75+
'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`
9276
})
9377

9478
this.collection = new ComponentCollection(
@@ -100,9 +84,7 @@ export class EastingNorthingField extends FormComponent {
10084
schema: {
10185
min: eastingMin,
10286
max: eastingMax,
103-
precision: 0,
104-
minLength: EASTING_MIN_LENGTH,
105-
maxLength: EASTING_MAX_LENGTH
87+
precision: 0
10688
},
10789
options: {
10890
required: isRequired,
@@ -118,9 +100,7 @@ export class EastingNorthingField extends FormComponent {
118100
schema: {
119101
min: northingMin,
120102
max: northingMax,
121-
precision: 0,
122-
minLength: NORTHING_MIN_LENGTH,
123-
maxLength: NORTHING_MAX_LENGTH
103+
precision: 0
124104
},
125105
options: {
126106
required: isRequired,

src/server/plugins/engine/components/LatLongField.test.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -578,15 +578,7 @@ describe('LatLongField', () => {
578578
value: getFormData({
579579
latitude: 52,
580580
longitude: -1
581-
}),
582-
errors: [
583-
expect.objectContaining({
584-
text: 'Latitude must have at least 1 decimal place'
585-
}),
586-
expect.objectContaining({
587-
text: 'Longitude must have at least 1 decimal place'
588-
})
589-
]
581+
})
590582
}
591583
},
592584
{
@@ -619,7 +611,6 @@ describe('LatLongField', () => {
619611
description: 'Length and precision validation',
620612
component: createLatLongComponent(),
621613
assertions: [
622-
// Latitude too short
623614
{
624615
input: getFormData({
625616
latitude: '52',
@@ -629,12 +620,7 @@ describe('LatLongField', () => {
629620
value: getFormData({
630621
latitude: 52,
631622
longitude: -1.5
632-
}),
633-
errors: [
634-
expect.objectContaining({
635-
text: 'Latitude must have at least 1 decimal place'
636-
})
637-
]
623+
})
638624
}
639625
},
640626
// Latitude too long
@@ -655,7 +641,6 @@ describe('LatLongField', () => {
655641
]
656642
}
657643
},
658-
// Longitude too short
659644
{
660645
input: getFormData({
661646
latitude: '52.1',
@@ -665,12 +650,7 @@ describe('LatLongField', () => {
665650
value: getFormData({
666651
latitude: 52.1,
667652
longitude: -1
668-
}),
669-
errors: [
670-
expect.objectContaining({
671-
text: 'Longitude must have at least 1 decimal place'
672-
})
673-
]
653+
})
674654
}
675655
},
676656
// Longitude too long

src/server/plugins/engine/components/LatLongField.ts

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,6 @@ import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
2626
// Precision constants
2727
// UK latitude/longitude requires high precision for accurate location (within ~11mm)
2828
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
4229

4330
export class LatLongField extends FormComponent {
4431
declare options: LatLongFieldComponent['options']
@@ -68,27 +55,21 @@ export class LatLongField extends FormComponent {
6855
'number.base': messageTemplate.objectMissing,
6956
'number.precision':
7057
'{{#label}} must have no more than 7 decimal places',
71-
'number.minPrecision':
72-
'{{#label}} must have at least {{#minPrecision}} decimal place',
7358
'number.unsafe': '{{#label}} must be a valid number'
7459
})
7560

7661
const latitudeMessages: LanguageMessages = convertToLanguageMessages({
7762
...customValidationMessages,
7863
'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,
7964
'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}`
8366
})
8467

8568
const longitudeMessages: LanguageMessages = convertToLanguageMessages({
8669
...customValidationMessages,
8770
'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,
8871
'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}`
9273
})
9374

9475
this.collection = new ComponentCollection(
@@ -100,10 +81,7 @@ export class LatLongField extends FormComponent {
10081
schema: {
10182
min: latitudeMin,
10283
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
10785
},
10886
options: {
10987
required: isRequired,
@@ -120,10 +98,7 @@ export class LatLongField extends FormComponent {
12098
schema: {
12199
min: longitudeMin,
122100
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
127102
},
128103
options: {
129104
required: isRequired,

src/server/plugins/engine/components/LocationFieldBase.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ interface LocationFieldOptions {
2828
interface ValidationConfig {
2929
pattern: RegExp
3030
patternErrorMessage: string
31-
customValidation?: (
32-
value: string,
33-
helpers: joi.CustomHelpers
34-
) => string | joi.ErrorReport
35-
additionalMessages?: LanguageMessages
3631
}
3732

3833
/**
@@ -71,14 +66,9 @@ export abstract class LocationFieldBase extends FormComponent {
7166
.required()
7267
.pattern(config.pattern)
7368
.messages({
74-
'string.pattern.base': config.patternErrorMessage,
75-
...config.additionalMessages
69+
'string.pattern.base': config.patternErrorMessage
7670
})
7771

78-
if (config.customValidation) {
79-
formSchema = formSchema.custom(config.customValidation)
80-
}
81-
8272
if (locationOptions.required === false) {
8373
formSchema = formSchema.allow('')
8474
}
@@ -91,10 +81,6 @@ export abstract class LocationFieldBase extends FormComponent {
9181
'string.pattern.base'
9282
]
9383

94-
if (config.additionalMessages) {
95-
messageKeys.push(...Object.keys(config.additionalMessages))
96-
}
97-
9884
const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {
9985
acc[key] = message
10086
return acc

src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,27 @@ describe('NationalGridFieldNumberField', () => {
9999
})
100100

101101
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'))
105117

106118
expect(result1.errors).toBeUndefined()
107119
expect(result2.errors).toBeUndefined()
108120
expect(result3.errors).toBeUndefined()
121+
expect(result4.errors).toBeUndefined()
122+
expect(result5.errors).toBeUndefined()
109123
})
110124

111125
it('formats values with spaces per GDS guidance', () => {
@@ -114,8 +128,8 @@ describe('NationalGridFieldNumberField', () => {
114128
const result3 = collection.validate(getFormData('NG12345,678'))
115129

116130
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')
119133
})
120134

121135
it('adds errors for empty value', () => {
@@ -258,15 +272,15 @@ describe('NationalGridFieldNumberField', () => {
258272
assertions: [
259273
{
260274
input: getFormData(' NG12345678'),
261-
output: { value: getFormData('NG 1234 5678') }
275+
output: { value: getFormData('NG12345678') }
262276
},
263277
{
264278
input: getFormData('NG12345678 '),
265-
output: { value: getFormData('NG 1234 5678') }
279+
output: { value: getFormData('NG12345678') }
266280
},
267281
{
268282
input: getFormData(' NG12345678 \n\n'),
269-
output: { value: getFormData('NG 1234 5678') }
283+
output: { value: getFormData('NG12345678') }
270284
}
271285
]
272286
},

src/server/plugins/engine/components/NationalGridFieldNumberField.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
2-
import type joi from 'joi'
32

43
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
54

65
export class NationalGridFieldNumberField extends LocationFieldBase {
76
declare options: NationalGridFieldNumberFieldComponent['options']
87

98
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}))$/
2715

28-
return formattedValue
29-
}
16+
return {
17+
pattern,
18+
patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`
3019
}
3120
}
3221

0 commit comments

Comments
 (0)