Skip to content

Commit af23392

Browse files
authored
Fix DF-529: lowercase short desc in location components (error preview in Designer) (#264)
* refactor: lowercase first letter in short desc for location components * test: update assertions for lowercase error messages * refactor: extract lowerFirst helper to reduce duplication * fix: resolve TypeScript linting errors in helpers test
1 parent 5df2539 commit af23392

11 files changed

Lines changed: 197 additions & 35 deletions

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,36 @@ describe('EastingNorthingField', () => {
416416
const staticResult = EastingNorthingField.getAllPossibleErrors()
417417
const instanceResult = field.getAllPossibleErrors()
418418

419-
expect(instanceResult).toEqual(staticResult)
419+
// Compare structure and content
420+
expect(instanceResult.baseErrors).toHaveLength(
421+
staticResult.baseErrors.length
422+
)
423+
expect(instanceResult.advancedSettingsErrors).toHaveLength(
424+
staticResult.advancedSettingsErrors.length
425+
)
426+
427+
// Compare error types
428+
expect(instanceResult.baseErrors.map((e) => e.type)).toEqual(
429+
staticResult.baseErrors.map((e) => e.type)
430+
)
431+
expect(
432+
instanceResult.advancedSettingsErrors.map((e) => e.type)
433+
).toEqual(staticResult.advancedSettingsErrors.map((e) => e.type))
434+
435+
// Compare rendered templates
436+
expect(
437+
instanceResult.baseErrors.map((e) =>
438+
typeof e.template === 'object' && 'rendered' in e.template
439+
? e.template.rendered
440+
: e.template
441+
)
442+
).toEqual(
443+
staticResult.baseErrors.map((e) =>
444+
typeof e.template === 'object' && 'rendered' in e.template
445+
? e.template.rendered
446+
: e.template
447+
)
448+
)
420449
})
421450
})
422451
})

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getLocationFieldViewModel
1616
} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'
1717
import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
18+
import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
1819
import { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.js'
1920
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
2021
import {
@@ -198,29 +199,41 @@ export class EastingNorthingField extends FormComponent {
198199
{ type: 'required', template: messageTemplate.required },
199200
{
200201
type: 'eastingFormat',
201-
template: 'Easting for {{#title}} must be between 1 and 6 digits'
202+
template: createLowerFirstExpression(
203+
'Easting for {{lowerFirst(#title)}} must be between 1 and 6 digits'
204+
)
202205
},
203206
{
204207
type: 'northingFormat',
205-
template: 'Northing for {{#title}} must be between 1 and 7 digits'
208+
template: createLowerFirstExpression(
209+
'Northing for {{lowerFirst(#title)}} must be between 1 and 7 digits'
210+
)
206211
}
207212
],
208213
advancedSettingsErrors: [
209214
{
210215
type: 'eastingMin',
211-
template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
216+
template: createLowerFirstExpression(
217+
`Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
218+
)
212219
},
213220
{
214221
type: 'eastingMax',
215-
template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
222+
template: createLowerFirstExpression(
223+
`Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
224+
)
216225
},
217226
{
218227
type: 'northingMin',
219-
template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
228+
template: createLowerFirstExpression(
229+
`Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
230+
)
220231
},
221232
{
222233
type: 'northingMax',
223-
template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
234+
template: createLowerFirstExpression(
235+
`Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
236+
)
224237
}
225238
]
226239
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,36 @@ describe('LatLongField', () => {
404404
const staticResult = LatLongField.getAllPossibleErrors()
405405
const instanceResult = field.getAllPossibleErrors()
406406

407-
expect(instanceResult).toEqual(staticResult)
407+
// Compare structure and content
408+
expect(instanceResult.baseErrors).toHaveLength(
409+
staticResult.baseErrors.length
410+
)
411+
expect(instanceResult.advancedSettingsErrors).toHaveLength(
412+
staticResult.advancedSettingsErrors.length
413+
)
414+
415+
// Compare error types
416+
expect(instanceResult.baseErrors.map((e) => e.type)).toEqual(
417+
staticResult.baseErrors.map((e) => e.type)
418+
)
419+
expect(
420+
instanceResult.advancedSettingsErrors.map((e) => e.type)
421+
).toEqual(staticResult.advancedSettingsErrors.map((e) => e.type))
422+
423+
// Compare rendered templates
424+
expect(
425+
instanceResult.baseErrors.map((e) =>
426+
typeof e.template === 'object' && 'rendered' in e.template
427+
? e.template.rendered
428+
: e.template
429+
)
430+
).toEqual(
431+
staticResult.baseErrors.map((e) =>
432+
typeof e.template === 'object' && 'rendered' in e.template
433+
? e.template.rendered
434+
: e.template
435+
)
436+
)
408437
})
409438
})
410439
})

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getLocationFieldViewModel
1313
} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'
1414
import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
15+
import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
1516
import { type LatLongState } from '~/src/server/plugins/engine/components/types.js'
1617
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
1718
import {
@@ -194,29 +195,41 @@ export class LatLongField extends FormComponent {
194195
{ type: 'required', template: messageTemplate.required },
195196
{
196197
type: 'latitudeFormat',
197-
template: 'Enter a valid latitude for {{#title}} like 51.519450'
198+
template: createLowerFirstExpression(
199+
'Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450'
200+
)
198201
},
199202
{
200203
type: 'longitudeFormat',
201-
template: 'Enter a valid longitude for {{#title}} like -0.127758'
204+
template: createLowerFirstExpression(
205+
'Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758'
206+
)
202207
}
203208
],
204209
advancedSettingsErrors: [
205210
{
206211
type: 'latitudeMin',
207-
template: 'Latitude for {{#title}} must be between 49 and 60'
212+
template: createLowerFirstExpression(
213+
'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'
214+
)
208215
},
209216
{
210217
type: 'latitudeMax',
211-
template: 'Latitude for {{#title}} must be between 49 and 60'
218+
template: createLowerFirstExpression(
219+
'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'
220+
)
212221
},
213222
{
214223
type: 'longitudeMin',
215-
template: 'Longitude for {{#title}} must be between -9 and 2'
224+
template: createLowerFirstExpression(
225+
'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'
226+
)
216227
},
217228
{
218229
type: 'longitudeMax',
219-
template: 'Longitude for {{#title}} must be between -9 and 2'
230+
template: createLowerFirstExpression(
231+
'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'
232+
)
220233
}
221234
]
222235
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { type FormComponentsDef } from '@defra/forms-model'
2-
import joi, { type LanguageMessages, type StringSchema } from 'joi'
2+
import joi, {
3+
type JoiExpression,
4+
type LanguageMessages,
5+
type StringSchema
6+
} from 'joi'
37

48
import {
59
FormComponent,
@@ -15,6 +19,7 @@ import {
1519
type FormSubmissionError,
1620
type FormSubmissionState
1721
} from '~/src/server/plugins/engine/types.js'
22+
import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
1823

1924
interface LocationFieldOptions {
2025
instructionText?: string
@@ -26,8 +31,8 @@ interface LocationFieldOptions {
2631

2732
interface ValidationConfig {
2833
pattern: RegExp
29-
patternErrorMessage: string
30-
requiredMessage?: string
34+
patternErrorMessage: JoiExpression
35+
requiredMessage?: JoiExpression
3136
}
3237

3338
/**
@@ -42,7 +47,7 @@ export abstract class LocationFieldBase extends FormComponent {
4247
protected abstract getValidationConfig(): ValidationConfig
4348
protected abstract getErrorTemplates(): {
4449
type: string
45-
template: string
50+
template: JoiExpression
4651
}[]
4752

4853
constructor(
@@ -61,11 +66,11 @@ export abstract class LocationFieldBase extends FormComponent {
6166
const requiredMessage =
6267
config.requiredMessage ?? (messageTemplate.required as string)
6368

64-
const messages: LanguageMessages = {
69+
const messages = convertToLanguageMessages({
6570
'any.required': requiredMessage,
6671
'string.empty': requiredMessage,
6772
'string.pattern.base': config.patternErrorMessage
68-
}
73+
})
6974

7075
let formSchema = joi
7176
.string()

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ describe('NationalGridFieldNumberField', () => {
129129

130130
expect(result.errors).toEqual([
131131
expect.objectContaining({
132-
text: 'Enter Example National Grid field number'
132+
text: 'Enter example National Grid field number'
133133
})
134134
])
135135
})
@@ -293,7 +293,7 @@ describe('NationalGridFieldNumberField', () => {
293293
value: getFormData('NG1234567'),
294294
errors: expect.arrayContaining([
295295
expect.objectContaining({
296-
text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
296+
text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
297297
})
298298
])
299299
}
@@ -304,7 +304,7 @@ describe('NationalGridFieldNumberField', () => {
304304
value: getFormData('N123456789'),
305305
errors: expect.arrayContaining([
306306
expect.objectContaining({
307-
text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
307+
text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
308308
})
309309
])
310310
}
@@ -315,7 +315,7 @@ describe('NationalGridFieldNumberField', () => {
315315
value: getFormData('NGABCDEFGH'),
316316
errors: expect.arrayContaining([
317317
expect.objectContaining({
318-
text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
318+
text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
319319
})
320320
])
321321
}

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
22

33
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4+
import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
45

56
export class NationalGridFieldNumberField extends LocationFieldBase {
67
declare options: NationalGridFieldNumberFieldComponent['options']
@@ -12,19 +13,25 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
1213
const pattern =
1314
/^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?([0-9]{4})\s?([0-9]{4})$/
1415

16+
const patternTemplate =
17+
'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'
18+
1519
return {
1620
pattern,
17-
patternErrorMessage: `Enter a valid National Grid field number for {{#title}} like NG 1234 5678`,
18-
requiredMessage: 'Enter {{#title}}'
21+
patternErrorMessage: createLowerFirstExpression(patternTemplate),
22+
requiredMessage: createLowerFirstExpression(
23+
'Enter {{lowerFirst(#title)}}'
24+
)
1925
}
2026
}
2127

2228
protected getErrorTemplates() {
2329
return [
2430
{
2531
type: 'pattern',
26-
template:
27-
'Enter a valid National Grid field number for {{#title}} like NG 1234 5678'
32+
template: createLowerFirstExpression(
33+
'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'
34+
)
2835
}
2936
]
3037
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('OsGridRefField', () => {
135135

136136
expect(result.errors).toEqual([
137137
expect.objectContaining({
138-
text: 'Enter Example OS grid reference'
138+
text: 'Enter example OS grid reference'
139139
})
140140
])
141141
})
@@ -308,7 +308,7 @@ describe('OsGridRefField', () => {
308308
value: getFormData('TQ12345'),
309309
errors: expect.arrayContaining([
310310
expect.objectContaining({
311-
text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
311+
text: 'Enter a valid OS grid reference for grid reference like TQ123456'
312312
})
313313
])
314314
}
@@ -319,7 +319,7 @@ describe('OsGridRefField', () => {
319319
value: getFormData('AA1234567'),
320320
errors: expect.arrayContaining([
321321
expect.objectContaining({
322-
text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
322+
text: 'Enter a valid OS grid reference for grid reference like TQ123456'
323323
})
324324
])
325325
}
@@ -330,7 +330,7 @@ describe('OsGridRefField', () => {
330330
value: getFormData('TQABCDEF'),
331331
errors: expect.arrayContaining([
332332
expect.objectContaining({
333-
text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
333+
text: 'Enter a valid OS grid reference for grid reference like TQ123456'
334334
})
335335
])
336336
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type OsGridRefFieldComponent } from '@defra/forms-model'
22

33
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4+
import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
45

56
export class OsGridRefField extends LocationFieldBase {
67
declare options: OsGridRefFieldComponent['options']
@@ -15,18 +16,25 @@ export class OsGridRefField extends LocationFieldBase {
1516
const pattern =
1617
/^((([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}))$/
1718

19+
const patternTemplate =
20+
'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'
21+
1822
return {
1923
pattern,
20-
patternErrorMessage: `Enter a valid OS grid reference for {{#title}} like TQ123456`,
21-
requiredMessage: 'Enter {{#title}}'
24+
patternErrorMessage: createLowerFirstExpression(patternTemplate),
25+
requiredMessage: createLowerFirstExpression(
26+
'Enter {{lowerFirst(#title)}}'
27+
)
2228
}
2329
}
2430

2531
protected getErrorTemplates() {
2632
return [
2733
{
2834
type: 'pattern',
29-
template: 'Enter a valid OS grid reference for {{#title}} like TQ123456'
35+
template: createLowerFirstExpression(
36+
'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'
37+
)
3038
}
3139
]
3240
}

0 commit comments

Comments
 (0)