From 6ad682ba9f9efbedbefd00e4a83b5f59bbd4fb88 Mon Sep 17 00:00:00 2001 From: David Stone Date: Tue, 11 Nov 2025 18:10:48 +0000 Subject: [PATCH 1/6] Fix location component error messages --- .../engine/components/EastingNorthingField.ts | 25 ++++++++++--------- .../plugins/engine/components/LatLongField.ts | 13 +++++----- .../NationalGridFieldNumberField.test.ts | 11 +++++--- .../NationalGridFieldNumberField.ts | 3 ++- .../engine/components/OsGridRefField.test.ts | 7 +++--- .../engine/components/OsGridRefField.ts | 3 ++- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/server/plugins/engine/components/EastingNorthingField.ts b/src/server/plugins/engine/components/EastingNorthingField.ts index 2a48efc08..cf26d456b 100644 --- a/src/server/plugins/engine/components/EastingNorthingField.ts +++ b/src/server/plugins/engine/components/EastingNorthingField.ts @@ -3,6 +3,7 @@ import { type EastingNorthingFieldComponent } from '@defra/forms-model' import { type LanguageMessages, type ObjectSchema } from 'joi' +import lowerFirst from 'lodash/lowerFirst.js' import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js' import { @@ -57,22 +58,22 @@ export class EastingNorthingField extends FormComponent { convertToLanguageMessages({ 'any.required': messageTemplate.objectMissing, 'number.base': messageTemplate.objectMissing, - 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${eastingMax}`, - 'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`, - 'number.precision': `{{#label}} for ${this.title} must be between 1 and 6 digits`, - 'number.integer': `{{#label}} for ${this.title} must be between 1 and 6 digits`, - 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits` + 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${eastingMax}`, + 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${eastingMin} and {{#limit}}`, + 'number.precision': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`, + 'number.integer': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`, + 'number.unsafe': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits` }) const northingValidationMessages: LanguageMessages = convertToLanguageMessages({ 'any.required': messageTemplate.objectMissing, 'number.base': messageTemplate.objectMissing, - 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${northingMax}`, - 'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`, - 'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`, - 'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`, - 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits` + 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${northingMax}`, + 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${northingMin} and {{#limit}}`, + 'number.precision': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`, + 'number.integer': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`, + 'number.unsafe': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits` }) this.collection = new ComponentCollection( @@ -198,11 +199,11 @@ export class EastingNorthingField extends FormComponent { advancedSettingsErrors: [ { type: 'eastingMin', - template: `Easting for [short description] must be between 0 and 700000` + template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}` }, { type: 'eastingMax', - template: `Easting for [short description] must be between 0 and 700000` + template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}` }, { type: 'northingMin', diff --git a/src/server/plugins/engine/components/LatLongField.ts b/src/server/plugins/engine/components/LatLongField.ts index f90beb541..fe43d3e1d 100644 --- a/src/server/plugins/engine/components/LatLongField.ts +++ b/src/server/plugins/engine/components/LatLongField.ts @@ -1,5 +1,6 @@ import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model' import { type LanguageMessages, type ObjectSchema } from 'joi' +import lowerFirst from 'lodash/lowerFirst.js' import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js' import { @@ -60,16 +61,16 @@ export class LatLongField extends FormComponent { const latitudeMessages: LanguageMessages = convertToLanguageMessages({ ...customValidationMessages, - 'number.base': `Enter a valid latitude for ${this.title} like 51.519450`, - 'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`, - 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}` + 'number.base': `Enter a valid latitude for ${lowerFirst(this.label)} like 51.519450`, + 'number.min': `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}`, + 'number.max': `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}` }) const longitudeMessages: LanguageMessages = convertToLanguageMessages({ ...customValidationMessages, - 'number.base': `Enter a valid longitude for ${this.title} like -0.127758`, - 'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`, - 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}` + 'number.base': `Enter a valid longitude for ${lowerFirst(this.label)} like -0.127758`, + 'number.min': `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}`, + 'number.max': `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}` }) this.collection = new ComponentCollection( diff --git a/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts b/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts index 2f52ca83d..be45dd17a 100644 --- a/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +++ b/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts @@ -265,6 +265,7 @@ describe('NationalGridFieldNumberField', () => { description: 'Trim empty spaces', component: { title: 'Example National Grid field number', + shortDescription: 'Grid field', name: 'myComponent', type: ComponentType.NationalGridFieldNumberField, options: {} @@ -288,6 +289,7 @@ describe('NationalGridFieldNumberField', () => { description: 'Pattern validation', component: { title: 'Example National Grid field number', + shortDescription: 'Grid field', name: 'myComponent', type: ComponentType.NationalGridFieldNumberField, options: {} @@ -299,7 +301,7 @@ describe('NationalGridFieldNumberField', () => { value: getFormData('NG1234567'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678' + text: 'Enter a valid National Grid field number for grid field like NG 1234 5678' }) ]) } @@ -310,7 +312,7 @@ describe('NationalGridFieldNumberField', () => { value: getFormData('N123456789'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678' + text: 'Enter a valid National Grid field number for grid field like NG 1234 5678' }) ]) } @@ -321,7 +323,7 @@ describe('NationalGridFieldNumberField', () => { value: getFormData('NGABCDEFGH'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid National Grid field number for Example National Grid field number like NG 1234 5678' + text: 'Enter a valid National Grid field number for grid field like NG 1234 5678' }) ]) } @@ -332,6 +334,7 @@ describe('NationalGridFieldNumberField', () => { description: 'Custom validation message', component: { title: 'Example National Grid field number', + shortDescription: 'Grid field', name: 'myComponent', type: ComponentType.NationalGridFieldNumberField, options: { @@ -367,6 +370,7 @@ describe('NationalGridFieldNumberField', () => { description: 'Custom validation messages (multiple)', component: { title: 'Example National Grid field number', + shortDescription: 'Grid field', name: 'myComponent', type: ComponentType.NationalGridFieldNumberField, options: { @@ -417,6 +421,7 @@ describe('NationalGridFieldNumberField', () => { description: 'Optional field', component: { title: 'Example National Grid field number', + shortDescription: 'Grid field', name: 'myComponent', type: ComponentType.NationalGridFieldNumberField, options: { diff --git a/src/server/plugins/engine/components/NationalGridFieldNumberField.ts b/src/server/plugins/engine/components/NationalGridFieldNumberField.ts index 19400d7e4..4331ccd99 100644 --- a/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +++ b/src/server/plugins/engine/components/NationalGridFieldNumberField.ts @@ -1,4 +1,5 @@ import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model' +import lowerFirst from 'lodash/lowerFirst.js' import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js' @@ -15,7 +16,7 @@ export class NationalGridFieldNumberField extends LocationFieldBase { return { pattern, - patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678` + patternErrorMessage: `Enter a valid National Grid field number for ${lowerFirst(this.label)} like NG 1234 5678` } } diff --git a/src/server/plugins/engine/components/OsGridRefField.test.ts b/src/server/plugins/engine/components/OsGridRefField.test.ts index 3af774b35..ab8db2fa6 100644 --- a/src/server/plugins/engine/components/OsGridRefField.test.ts +++ b/src/server/plugins/engine/components/OsGridRefField.test.ts @@ -284,6 +284,7 @@ describe('OsGridRefField', () => { description: 'Pattern validation', component: { title: 'Example OS grid reference', + shortDescription: 'Grid reference', name: 'myComponent', type: ComponentType.OsGridRefField, options: {} @@ -295,7 +296,7 @@ describe('OsGridRefField', () => { value: getFormData('TQ12345'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456' + text: 'Enter a valid OS grid reference for grid reference like TQ123456' }) ]) } @@ -306,7 +307,7 @@ describe('OsGridRefField', () => { value: getFormData('AA1234567'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456' + text: 'Enter a valid OS grid reference for grid reference like TQ123456' }) ]) } @@ -317,7 +318,7 @@ describe('OsGridRefField', () => { value: getFormData('TQABCDEF'), errors: expect.arrayContaining([ expect.objectContaining({ - text: 'Enter a valid OS grid reference for Example OS grid reference like TQ123456' + text: 'Enter a valid OS grid reference for grid reference like TQ123456' }) ]) } diff --git a/src/server/plugins/engine/components/OsGridRefField.ts b/src/server/plugins/engine/components/OsGridRefField.ts index 5a27e8147..8f2a585aa 100644 --- a/src/server/plugins/engine/components/OsGridRefField.ts +++ b/src/server/plugins/engine/components/OsGridRefField.ts @@ -1,4 +1,5 @@ import { type OsGridRefFieldComponent } from '@defra/forms-model' +import lowerFirst from 'lodash/lowerFirst.js' import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js' @@ -14,7 +15,7 @@ export class OsGridRefField extends LocationFieldBase { return { pattern, - patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456` + patternErrorMessage: `Enter a valid OS grid reference for ${lowerFirst(this.label)} like TQ123456` } } From fd3d09d24ddea86fd6db9bdf29c11947b086d377 Mon Sep 17 00:00:00 2001 From: David Stone Date: Tue, 11 Nov 2025 20:42:25 +0000 Subject: [PATCH 2/6] Update OsGridRef precision --- .../engine/components/OsGridRefField.test.ts | 18 +++++++++++++++--- .../engine/components/OsGridRefField.ts | 9 ++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/server/plugins/engine/components/OsGridRefField.test.ts b/src/server/plugins/engine/components/OsGridRefField.test.ts index ab8db2fa6..9183fb081 100644 --- a/src/server/plugins/engine/components/OsGridRefField.test.ts +++ b/src/server/plugins/engine/components/OsGridRefField.test.ts @@ -100,12 +100,22 @@ describe('OsGridRefField', () => { const result1 = collection.validate(getFormData('SD865005')) const result2 = collection.validate(getFormData('SD 865 005')) + const result3 = collection.validate(getFormData('SD86565005')) + const result4 = collection.validate(getFormData('SD 8656 0065')) + + const result5 = collection.validate(getFormData('SD8654454005')) + const result6 = collection.validate(getFormData('SD 86455 00545')) + // Test case-insensitive - const result3 = collection.validate(getFormData('nt123456')) + const result7 = collection.validate(getFormData('nt123456')) expect(result1.errors).toBeUndefined() expect(result2.errors).toBeUndefined() expect(result3.errors).toBeUndefined() + expect(result4.errors).toBeUndefined() + expect(result5.errors).toBeUndefined() + expect(result6.errors).toBeUndefined() + expect(result7.errors).toBeUndefined() }) it('retains values with spaces per GDS guidance', () => { @@ -137,8 +147,9 @@ describe('OsGridRefField', () => { const result4 = collection.validate(getFormData('TQ1234567')) // Wrong number of digits (7) // Test mismatched digit counts (must be either 3+3, 4+4 or 5+5, not mixed) - const result5 = collection.validate(getFormData('SN 4444 55555')) // mismatched digit counts - const result6 = collection.validate(getFormData('SN 55555 4444')) // mismatched digit counts + const result5 = collection.validate(getFormData('SN 333 4444')) // mismatched digit counts + const result6 = collection.validate(getFormData('SN 4444 55555')) // mismatched digit counts + const result7 = collection.validate(getFormData('SN 55555 4444')) // mismatched digit counts expect(result1.errors).toBeTruthy() expect(result2.errors).toBeTruthy() @@ -146,6 +157,7 @@ describe('OsGridRefField', () => { expect(result4.errors).toBeTruthy() expect(result5.errors).toBeTruthy() expect(result6.errors).toBeTruthy() + expect(result7.errors).toBeTruthy() }) }) diff --git a/src/server/plugins/engine/components/OsGridRefField.ts b/src/server/plugins/engine/components/OsGridRefField.ts index 8f2a585aa..feada2d1c 100644 --- a/src/server/plugins/engine/components/OsGridRefField.ts +++ b/src/server/plugins/engine/components/OsGridRefField.ts @@ -7,11 +7,14 @@ export class OsGridRefField extends LocationFieldBase { declare options: OsGridRefFieldComponent['options'] protected getValidationConfig() { - // Regex for OS grid references and parcel IDs + // Regex for OS grid references (NGR) // Validates specific valid OS grid letter combinations with: - // - 2 letters & 6 digits (e.g., SD865005 or SD 865 005) + // - 2 letters & 6 digits in 2 blocks of 3 e.g. ST 678 678 + // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789 + // - 2 letters & 10 digits in 2 blocks of 5 e.g. SO 12345 12345 + // Optional spaces between each block const pattern = - /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3}))$/ + /^((([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}))$/ return { pattern, From 5e0230957f7656aeedf686114c2c81ba4197d412 Mon Sep 17 00:00:00 2001 From: David Stone Date: Tue, 11 Nov 2025 20:50:20 +0000 Subject: [PATCH 3/6] Remove NGFR 10 digit --- .../components/NationalGridFieldNumberField.test.ts | 10 +--------- .../engine/components/NationalGridFieldNumberField.ts | 7 +++---- src/server/plugins/engine/components/OsGridRefField.ts | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts b/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts index be45dd17a..00a67d634 100644 --- a/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +++ b/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts @@ -103,23 +103,15 @@ describe('NationalGridFieldNumberField', () => { const result1 = collection.validate(getFormData('TQ12345678')) const result2 = collection.validate(getFormData('TQ 1234 5678')) - // Test 10-digit OS grid reference format (2x5) - const result3 = collection.validate(getFormData('SU1234567890')) - const result4 = collection.validate(getFormData('SU 12345 67890')) - expect(result1.errors).toBeUndefined() expect(result2.errors).toBeUndefined() - expect(result3.errors).toBeUndefined() - expect(result4.errors).toBeUndefined() // Test case-insensitive - const result5 = collection.validate(getFormData('nt12345678')) + const result3 = collection.validate(getFormData('nt12345678')) expect(result1.errors).toBeUndefined() expect(result2.errors).toBeUndefined() expect(result3.errors).toBeUndefined() - expect(result4.errors).toBeUndefined() - expect(result5.errors).toBeUndefined() }) it('formats values with spaces per GDS guidance', () => { diff --git a/src/server/plugins/engine/components/NationalGridFieldNumberField.ts b/src/server/plugins/engine/components/NationalGridFieldNumberField.ts index 4331ccd99..6272cb25a 100644 --- a/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +++ b/src/server/plugins/engine/components/NationalGridFieldNumberField.ts @@ -7,12 +7,11 @@ export class NationalGridFieldNumberField extends LocationFieldBase { declare options: NationalGridFieldNumberFieldComponent['options'] protected getValidationConfig() { - // Regex for OS grid references and parcel IDs + // Regex for OS national grid field references (NGFR) // Validates specific valid OS grid letter combinations with: - // - 2 letters & 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789 - // - 2 letters & 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345 + // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789 const pattern = - /^((([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}))$/ + /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?([0-9]{4})\s?([0-9]{4})$/ return { pattern, diff --git a/src/server/plugins/engine/components/OsGridRefField.ts b/src/server/plugins/engine/components/OsGridRefField.ts index feada2d1c..5902a15ea 100644 --- a/src/server/plugins/engine/components/OsGridRefField.ts +++ b/src/server/plugins/engine/components/OsGridRefField.ts @@ -7,7 +7,7 @@ export class OsGridRefField extends LocationFieldBase { declare options: OsGridRefFieldComponent['options'] protected getValidationConfig() { - // Regex for OS grid references (NGR) + // Regex for OS national grid references (NGR) // Validates specific valid OS grid letter combinations with: // - 2 letters & 6 digits in 2 blocks of 3 e.g. ST 678 678 // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789 From 38115f092b765f30572b485d9ad138ffd768a41c Mon Sep 17 00:00:00 2001 From: Mohammed Khalid Date: Wed, 12 Nov 2025 09:56:02 +0000 Subject: [PATCH 4/6] Fix typo in latitude and longitude title and hint in unicorn breeder registration form --- src/server/forms/register-as-a-unicorn-breeder.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/forms/register-as-a-unicorn-breeder.yaml b/src/server/forms/register-as-a-unicorn-breeder.yaml index 2263aebfc..fcfb373c4 100644 --- a/src/server/forms/register-as-a-unicorn-breeder.yaml +++ b/src/server/forms/register-as-a-unicorn-breeder.yaml @@ -174,9 +174,9 @@ pages: options: {} schema: {} type: LatLongField - title: Latitute and longitude + title: Latitude and longitude hint: - This is an Latitute and Longitude component + This is an Latitude and Longitude component - name: bhjloS options: {} schema: {} From 7cb9a33e52889e4912f2aeed34a4031a24d9f98b Mon Sep 17 00:00:00 2001 From: Mohammed Khalid Date: Thu, 13 Nov 2025 09:52:37 +0000 Subject: [PATCH 5/6] fix DF-529: errors inline on Location components (#245) * feat: errors inline on Location components - Improved error message handling in `LocationFieldHelpers` by including `errorMessage` in the view model. - Modified HTML template to display error messages and adjust form group classes based on error presence. * lint: fixes * test: update tests * fix: remove unnecessary whitespace in location field template * style: Updated flex properties for better layout on desktop and mobile views. * style: refactor location input styles and HTML structure for improved error handling and layout - Updated SCSS for error states in location input fields, ensuring consistent padding and margin. - Refactored HTML structure to utilize grid layout for better responsiveness and alignment of input items. * style: add margin-top to location input grid row for improved spacing * style: remove unused location input styles * Remove two thirds wrapper * style: remove unused location input imports from references --------- Co-authored-by: David Stone --- src/client/stylesheets/_location-input.scss | 60 ------------------- src/client/stylesheets/application.scss | 1 - src/client/stylesheets/shared.scss | 1 - .../components/EastingNorthingField.test.ts | 12 +++- .../engine/components/LatLongField.test.ts | 12 +++- .../components/LocationFieldHelpers.test.ts | 19 ++++-- .../engine/components/LocationFieldHelpers.ts | 12 +--- src/server/plugins/engine/components/types.ts | 3 + .../components/_location-field-base.html | 36 ++++++----- 9 files changed, 60 insertions(+), 96 deletions(-) delete mode 100644 src/client/stylesheets/_location-input.scss diff --git a/src/client/stylesheets/_location-input.scss b/src/client/stylesheets/_location-input.scss deleted file mode 100644 index 5cf52f72b..000000000 --- a/src/client/stylesheets/_location-input.scss +++ /dev/null @@ -1,60 +0,0 @@ -@use "govuk-frontend" as *; - -.app-location-input { - @include govuk-clearfix; - font-size: 0; // removes whitespace caused by inline-block - margin-bottom: govuk-spacing(6); - - &:has(.govuk-input--error) { - border-left: $govuk-border-width-form-group-error solid $govuk-error-colour; - padding-left: govuk-spacing(3); - margin-top: 0; - } -} - -.govuk-hint:has(+ .app-location-input .govuk-input--error) { - border-left: $govuk-border-width-form-group-error solid $govuk-error-colour; - padding-left: govuk-spacing(3); - margin-bottom: 0; -} - -.govuk-fieldset:has(.app-location-input .govuk-input--error) { - .govuk-fieldset__legend { - border-left: $govuk-border-width-form-group-error solid $govuk-error-colour; - padding-left: govuk-spacing(3); - margin-bottom: 0; - } - - .govuk-fieldset__legend + .govuk-hint { - margin-top: 0; - } -} - -.app-location-input__item { - display: inline-block; - margin-right: govuk-spacing(4); - margin-bottom: govuk-spacing(4); - - &:last-child { - margin-right: 0; - } - - @include govuk-media-query($from: tablet) { - margin-bottom: 0; - } - - .govuk-form-group { - margin-bottom: 0; - display: inline-block; - width: auto; - } - - .govuk-label { - display: block; - } - - .govuk-input { - margin-bottom: 0; - width: auto; - } -} diff --git a/src/client/stylesheets/application.scss b/src/client/stylesheets/application.scss index bb90268a1..349c344c2 100644 --- a/src/client/stylesheets/application.scss +++ b/src/client/stylesheets/application.scss @@ -2,7 +2,6 @@ @use "shared"; @use "code"; @use "tag-env"; -@use "location-input"; // An example of some user-supplied styling // Not great practice but it illustrates the point diff --git a/src/client/stylesheets/shared.scss b/src/client/stylesheets/shared.scss index daeadccba..cb7277959 100644 --- a/src/client/stylesheets/shared.scss +++ b/src/client/stylesheets/shared.scss @@ -2,7 +2,6 @@ @use "pkg:accessible-autocomplete"; @use "prose"; @use "summary-list"; -@use "location-input"; // Use default GDS Transport font for autocomplete .autocomplete__hint, diff --git a/src/server/plugins/engine/components/EastingNorthingField.test.ts b/src/server/plugins/engine/components/EastingNorthingField.test.ts index 8cbebf244..3777eb7b1 100644 --- a/src/server/plugins/engine/components/EastingNorthingField.test.ts +++ b/src/server/plugins/engine/components/EastingNorthingField.test.ts @@ -335,7 +335,7 @@ describe('EastingNorthingField', () => { expect(instructionText).toContain('meters') }) - it('sets error classes when component has errors', () => { + it('handles errors when component has validation errors', () => { const payload = getFormData({ easting: '', northing: '' @@ -352,15 +352,21 @@ describe('EastingNorthingField', () => { const viewModel = field.getViewModel(payload, errors) + // Check that error is passed to the viewModel + expect(viewModel.errors).toEqual(errors) + + // Items should be present with their basic structure expect(viewModel.items?.[0]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__easting', + name: 'myComponent__easting' }) ) expect(viewModel.items?.[1]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__northing', + name: 'myComponent__northing' }) ) }) diff --git a/src/server/plugins/engine/components/LatLongField.test.ts b/src/server/plugins/engine/components/LatLongField.test.ts index 6536506f6..57bac38e4 100644 --- a/src/server/plugins/engine/components/LatLongField.test.ts +++ b/src/server/plugins/engine/components/LatLongField.test.ts @@ -324,7 +324,7 @@ describe('LatLongField', () => { expect(instructionText).toContain('decimal') }) - it('sets error classes when component has errors', () => { + it('handles errors when component has validation errors', () => { const payload = getFormData({ latitude: '', longitude: '' @@ -341,15 +341,21 @@ describe('LatLongField', () => { const viewModel = field.getViewModel(payload, errors) + // Check that error is passed to the viewModel + expect(viewModel.errors).toEqual(errors) + + // Items should be present with their basic structure expect(viewModel.items?.[0]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__latitude', + name: 'myComponent__latitude' }) ) expect(viewModel.items?.[1]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__longitude', + name: 'myComponent__longitude' }) ) }) diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.test.ts b/src/server/plugins/engine/components/LocationFieldHelpers.test.ts index 3adf8fee7..1cf77a087 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.test.ts @@ -71,7 +71,7 @@ describe('LocationFieldHelpers', () => { expect(instructionText).toContain('decimal format') }) - it('should add error classes to items when component has errors', () => { + it('should handle component-level errors correctly', () => { const def: LatLongFieldComponent = { title: 'Example lat long', name: 'myComponent', @@ -99,20 +99,26 @@ describe('LocationFieldHelpers', () => { const viewModel = field.getViewModel(payload, errors) + // Check that errors are passed to the viewModel + expect(viewModel.errors).toEqual(errors) + + // Items should still have their structure expect(viewModel.items[0]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__latitude', + name: 'myComponent__latitude' }) ) expect(viewModel.items[1]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + id: 'myComponent__longitude', + name: 'myComponent__longitude' }) ) }) - it('should add error classes to items when subfield has errors', () => { + it('should pass error messages to individual items when subfield has errors', () => { const def: LatLongFieldComponent = { title: 'Example lat long', name: 'myComponent', @@ -140,9 +146,12 @@ describe('LocationFieldHelpers', () => { const viewModel = field.getViewModel(payload, errors) + // Check that errorMessage is passed through to the item expect(viewModel.items[0]).toEqual( expect.objectContaining({ - classes: expect.stringContaining('govuk-input--error') + errorMessage: { + text: 'Invalid latitude' + } }) ) }) diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.ts b/src/server/plugins/engine/components/LocationFieldHelpers.ts index ec721df2c..7e1e70a0a 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.ts @@ -30,12 +30,9 @@ export function getLocationFieldViewModel( payload: FormPayload, errors?: FormSubmissionError[] ) { - const { collection, name } = component + const { collection } = component const { fieldset: existingFieldset, label } = viewModel - // Check for component errors only - const hasError = errors?.some((error) => error.name === name) - // Use the component collection to generate the subitems const items: DateInputItem[] = collection .getViewModel(payload, errors) @@ -46,10 +43,6 @@ export function getLocationFieldViewModel( label.toString = () => label.text // Use string labels } - if (hasError || errorMessage) { - classes = `${classes ?? ''} govuk-input--error`.trim() - } - // Allow any `toString()`-able value so non-numeric // values are shown alongside their error messages if (!isFormValue(value)) { @@ -64,7 +57,8 @@ export function getLocationFieldViewModel( value, classes, prefix, - suffix + suffix, + errorMessage } }) diff --git a/src/server/plugins/engine/components/types.ts b/src/server/plugins/engine/components/types.ts index 0d8e37266..0f369d483 100644 --- a/src/server/plugins/engine/components/types.ts +++ b/src/server/plugins/engine/components/types.ts @@ -64,6 +64,9 @@ export interface DateInputItem { // but not by date fields. This interface is reused by both component types. prefix?: ComponentText suffix?: ComponentText + errorMessage?: { + text: string + } condition?: undefined } diff --git a/src/server/plugins/engine/views/components/_location-field-base.html b/src/server/plugins/engine/views/components/_location-field-base.html index df6dbfffc..742a9b448 100644 --- a/src/server/plugins/engine/views/components/_location-field-base.html +++ b/src/server/plugins/engine/views/components/_location-field-base.html @@ -12,22 +12,22 @@ }) }} {% endif %} -
+
{% for item in component.model.items %} -
+
{{ govukInput({ id: item.id, name: item.name, label: { - text: item.label, - classes: "govuk-label--s" + text: item.label }, classes: item.classes, value: item.value, type: inputType, inputmode: inputMode, prefix: item.prefix, - suffix: item.suffix + suffix: item.suffix, + errorMessage: item.errorMessage }) }}
{% endfor %} @@ -41,13 +41,21 @@ {% endif %} {% endset %} - {{ govukFieldset({ - legend: { - text: component.model.fieldset.legend.text, - classes: component.model.fieldset.legend.classes, - isPageHeading: false - }, - html: fieldsetHtml - }) }} -{% endmacro %} + {% set hasErrors = false %} + {% for item in component.model.items %} + {% if item.errorMessage %} + {% set hasErrors = true %} + {% endif %} + {% endfor %} +
+ {{ govukFieldset({ + legend: { + text: component.model.fieldset.legend.text, + classes: component.model.fieldset.legend.classes, + isPageHeading: false + }, + html: fieldsetHtml + }) }} +
+{% endmacro %} From 0a8b6aef2348101334177b7c21e3ce0e0850cae4 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 13 Nov 2025 09:59:21 +0000 Subject: [PATCH 6/6] Add short description to location components in the unicorn form --- src/server/forms/register-as-a-unicorn-breeder.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/forms/register-as-a-unicorn-breeder.yaml b/src/server/forms/register-as-a-unicorn-breeder.yaml index fcfb373c4..ff8408dde 100644 --- a/src/server/forms/register-as-a-unicorn-breeder.yaml +++ b/src/server/forms/register-as-a-unicorn-breeder.yaml @@ -168,6 +168,7 @@ pages: schema: {} type: EastingNorthingField title: Easting and northing + shortDescription: Location hint: This is an Easting and Northing component - name: seTThb @@ -175,6 +176,7 @@ pages: schema: {} type: LatLongField title: Latitude and longitude + shortDescription: Position hint: This is an Latitude and Longitude component - name: bhjloS