Skip to content

Commit 13d1cf3

Browse files
feat: Update latitude and longitude validation ranges in LatLongField (#238)
* feat: Update latitude and longitude validation ranges in LatLongField * refactor: sonar changes (coverage and duplicated lines) * 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 --------- Co-authored-by: David Stone <davidjamesstone@gmail.com>
1 parent 836051e commit 13d1cf3

12 files changed

Lines changed: 82 additions & 848 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: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ describe('LatLongField', () => {
149149

150150
const result2 = collection.validate(
151151
getFormData({
152-
latitude: '49.1',
152+
latitude: '50.5',
153153
longitude: '-8.9'
154154
})
155155
)
@@ -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: 8 additions & 33 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']
@@ -57,38 +44,32 @@ export class LatLongField extends FormComponent {
5744
const isRequired = options.required !== false
5845

5946
// 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
6451

6552
const customValidationMessages: LanguageMessages =
6653
convertToLanguageMessages({
6754
'any.required': messageTemplate.objectMissing,
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
},

0 commit comments

Comments
 (0)