Skip to content

Commit 8f51f91

Browse files
DF-1031: Update TelephoneNumberField validation (#416)
* Update TelephoneNumberField validation * Update telephone error message * Bump @defra/forms-model@3.0.674 * Fix TelephoneNumber component tests * Fix test linting
1 parent 2bd513c commit 8f51f91

9 files changed

Lines changed: 305 additions & 22 deletions

File tree

package-lock.json

Lines changed: 22 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
},
8888
"license": "SEE LICENSE IN LICENSE",
8989
"dependencies": {
90-
"@defra/forms-model": "^3.0.668",
90+
"@defra/forms-model": "^3.0.674",
9191
"@defra/hapi-tracing": "^1.29.0",
9292
"@defra/interactive-map": "^0.0.22-alpha",
9393
"@elastic/ecs-pino-format": "^1.5.0",
@@ -119,6 +119,7 @@
119119
"dotenv": "^17.2.3",
120120
"expr-eval-fork": "^3.0.0",
121121
"geodesy": "^2.4.0",
122+
"google-libphonenumber": "^3.2.44",
122123
"govuk-frontend": "^5.13.0",
123124
"hapi-pino": "^13.0.0",
124125
"hapi-pulse": "^3.0.1",
@@ -157,6 +158,7 @@
157158
"@types/btoa": "^1.2.5",
158159
"@types/convict": "^6.1.6",
159160
"@types/eslint": "^9.6.1",
161+
"@types/google-libphonenumber": "^7.4.30",
160162
"@types/govuk-frontend": "^5.11.0",
161163
"@types/hapi": "^18.0.15",
162164
"@types/hapi__catbox-memory": "^6.0.2",

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ComponentType,
3+
type ComponentDef,
34
type TelephoneNumberFieldComponent
45
} from '@defra/forms-model'
56

@@ -8,6 +9,11 @@ import {
89
getAnswer,
910
type Field
1011
} from '~/src/server/plugins/engine/components/helpers/components.js'
12+
import {
13+
INTERNATIONAL_ERROR_CODE,
14+
INVALID_ERROR_CODE,
15+
UK_ERROR_CODE
16+
} from '~/src/server/plugins/engine/components/helpers/telephone.js'
1117
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
1218
import definition from '~/test/form/definitions/blank.js'
1319
import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
@@ -99,19 +105,19 @@ describe('TelephoneNumberField', () => {
99105
})
100106

101107
it.each([
102-
'+111-111-11',
103-
'+111 111 11',
104-
'+11111111',
108+
'+1 213 555-0123',
109+
'+1213555-0123',
110+
'+12135550123',
105111
'+44 7930 111 222',
106112
'07930 111 222',
107113
'01606 76543',
108114
'01606 765432',
109-
'0203 765 443',
115+
'0203 005 1234',
110116
'0800 123 321',
111117
'(01606) 765432',
112118
'(01606) 765-432',
113119
'01606 765-432',
114-
'+44203-765-443',
120+
'+44203-005-1234',
115121
'0800123-321',
116122
'0800-123-321'
117123
])("accepts valid value '%s'", (value) => {
@@ -308,7 +314,10 @@ describe('TelephoneNumberField', () => {
308314
customValidationMessages: {
309315
'any.required': 'This is a custom required error',
310316
'string.empty': 'This is a custom empty string error',
311-
'string.pattern.base': 'This is a custom pattern error'
317+
'string.pattern.base': 'This is a custom pattern error',
318+
[INVALID_ERROR_CODE]: 'This is a custom pattern error',
319+
[UK_ERROR_CODE]: 'This is a custom pattern error',
320+
[INTERNATIONAL_ERROR_CODE]: 'This is a custom pattern error'
312321
}
313322
}
314323
} satisfies TelephoneNumberFieldComponent,
@@ -369,7 +378,7 @@ describe('TelephoneNumberField', () => {
369378
let collection: ComponentCollection
370379

371380
beforeEach(() => {
372-
collection = new ComponentCollection([def], { model })
381+
collection = new ComponentCollection([def as ComponentDef], { model })
373382
})
374383

375384
it.each([...assertions])(
@@ -382,3 +391,7 @@ describe('TelephoneNumberField', () => {
382391
})
383392
})
384393
})
394+
395+
/**
396+
* @import { ComponentDef } from '@defra/forms-model'
397+
*/

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { type TelephoneNumberFieldComponent } from '@defra/forms-model'
2-
import joi, { type StringSchema } from 'joi'
2+
import { type StringSchema } from 'joi'
33

44
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
55
import { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers/index.js'
6+
import {
7+
INTERNATIONAL_ERROR_CODE,
8+
INVALID_ERROR_CODE,
9+
UK_ERROR_CODE,
10+
joi
11+
} from '~/src/server/plugins/engine/components/helpers/telephone.js'
612
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
713
import {
814
type ErrorMessageTemplateList,
915
type FormPayload,
1016
type FormSubmissionError
1117
} from '~/src/server/plugins/engine/types.js'
1218

13-
const PATTERN = /^[0-9\\\s+()-]*$/
14-
1519
export class TelephoneNumberField extends FormComponent {
1620
declare options: TelephoneNumberFieldComponent['options']
1721
declare formSchema: StringSchema
@@ -24,11 +28,12 @@ export class TelephoneNumberField extends FormComponent {
2428
super(def, props)
2529

2630
const { options } = def
31+
const { format } = options
2732

2833
let formSchema = joi
2934
.string()
3035
.trim()
31-
.pattern(PATTERN)
36+
.phoneNumber({ format })
3237
.label(this.label)
3338
.required()
3439

@@ -42,7 +47,10 @@ export class TelephoneNumberField extends FormComponent {
4247
formSchema = formSchema.messages({
4348
'any.required': message,
4449
'string.empty': message,
45-
'string.pattern.base': message
50+
'string.pattern.base': message,
51+
[INVALID_ERROR_CODE]: message,
52+
[UK_ERROR_CODE]: message,
53+
[INTERNATIONAL_ERROR_CODE]: message
4654
})
4755
} else if (options.customValidationMessages) {
4856
formSchema = formSchema.messages(options.customValidationMessages)

src/server/plugins/engine/components/helpers/geospatial.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,3 @@ export function getGeospatialSchema(
171171
def
172172
)
173173
}
174-
175-
/**
176-
* @import { CustomHelpers } from 'joi'
177-
*/
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { TelephoneNumberFieldOptionsFormatEnum } from '@defra/forms-model'
2+
3+
import { joi } from '~/src/server/plugins/engine/components/helpers/telephone.js'
4+
5+
describe('Telephone validation helpers', () => {
6+
describe('UK numbers', () => {
7+
test('it should not have errors for valid telephone number', () => {
8+
const telephoneSchema = joi
9+
.string()
10+
.phoneNumber({ format: TelephoneNumberFieldOptionsFormatEnum.UK })
11+
.label('Home phone')
12+
13+
const result = telephoneSchema.validate('0160676477')
14+
15+
expect(result.error).toBeUndefined()
16+
expect(result.value).toBe('0160676477')
17+
})
18+
19+
test('it should not have errors for valid UK telephone number in international format', () => {
20+
const telephoneSchema = joi
21+
.string()
22+
.phoneNumber({ format: TelephoneNumberFieldOptionsFormatEnum.UK })
23+
.label('Home phone')
24+
25+
const result = telephoneSchema.validate('+44 1606 76477')
26+
27+
expect(result.error).toBeUndefined()
28+
expect(result.value).toBe('+44 1606 76477')
29+
})
30+
31+
test('it should have errors for invalid telephone number', () => {
32+
const telephoneSchema = joi
33+
.string()
34+
.phoneNumber({ format: TelephoneNumberFieldOptionsFormatEnum.UK })
35+
.label('Home phone')
36+
37+
const result = telephoneSchema.validate('ABC')
38+
39+
expect(result.error).toBeDefined()
40+
expect(result.error?.message).toBe(
41+
'Enter home phone, like 01632 960 001, 07700 900 982 or +44 808 157 0192'
42+
)
43+
expect(result.value).toBe('ABC')
44+
})
45+
46+
test('it should have errors for international telephone number', () => {
47+
const telephoneSchema = joi
48+
.string()
49+
.phoneNumber({ format: TelephoneNumberFieldOptionsFormatEnum.UK })
50+
.label('Home phone')
51+
52+
const result = telephoneSchema.validate('+1-212-456-7890')
53+
54+
expect(result.error).toBeDefined()
55+
expect(result.error?.message).toBe(
56+
'Enter home phone, like 01632 960 001, 07700 900 982 or +44 808 157 0192'
57+
)
58+
expect(result.value).toBe('+1-212-456-7890')
59+
})
60+
})
61+
62+
describe('International numbers', () => {
63+
test('it should not have errors for valid telephone number', () => {
64+
const telephoneSchema = joi
65+
.string()
66+
.phoneNumber({
67+
format: TelephoneNumberFieldOptionsFormatEnum.International
68+
})
69+
.label('Home phone')
70+
71+
const result = telephoneSchema.validate('+1-212-456-7890')
72+
73+
expect(result.error).toBeUndefined()
74+
expect(result.value).toBe('+1-212-456-7890')
75+
})
76+
77+
test('it should have errors for invalid telephone number', () => {
78+
const telephoneSchema = joi
79+
.string()
80+
.phoneNumber({
81+
format: TelephoneNumberFieldOptionsFormatEnum.International
82+
})
83+
.label('Home phone')
84+
85+
const result = telephoneSchema.validate('ABC')
86+
87+
expect(result.error).toBeDefined()
88+
expect(result.error?.message).toBe(
89+
'Enter home phone, starting with + and the country code, for example +92333 1234567 or 00923331234567'
90+
)
91+
expect(result.value).toBe('ABC')
92+
})
93+
94+
test('it should have errors for UK phone number in international format', () => {
95+
const telephoneSchema = joi
96+
.string()
97+
.phoneNumber({
98+
format: TelephoneNumberFieldOptionsFormatEnum.International
99+
})
100+
.label('Home phone')
101+
102+
const result = telephoneSchema.validate('+44 1606 76477')
103+
104+
expect(result.error).toBeDefined()
105+
expect(result.error?.message).toBe(
106+
'Enter home phone, starting with + and the country code, for example +92333 1234567 or 00923331234567'
107+
)
108+
expect(result.value).toBe('+44 1606 76477')
109+
})
110+
})
111+
112+
describe('Any format', () => {
113+
test('it should not have errors for valid national telephone number', () => {
114+
const telephoneSchema = joi.string().phoneNumber().label('Home phone')
115+
116+
const result = telephoneSchema.validate('0160676477')
117+
118+
expect(result.error).toBeUndefined()
119+
expect(result.value).toBe('0160676477')
120+
})
121+
122+
test('it should not have errors for valid international telephone number', () => {
123+
const telephoneSchema = joi.string().phoneNumber().label('Home phone')
124+
125+
const result = telephoneSchema.validate('+1-212-456-7890')
126+
127+
expect(result.error).toBeUndefined()
128+
expect(result.value).toBe('+1-212-456-7890')
129+
})
130+
131+
test('it should have errors for invalid telephone number', () => {
132+
const telephoneSchema = joi.string().phoneNumber().label('Home phone')
133+
134+
const result = telephoneSchema.validate('ABC')
135+
136+
expect(result.error).toBeDefined()
137+
expect(result.error?.message).toBe(
138+
'Enter home phone, like 01632 960 001, 07700 900 982, +44 808 157 0192 or +924568456136'
139+
)
140+
expect(result.value).toBe('ABC')
141+
})
142+
})
143+
})

0 commit comments

Comments
 (0)