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 %}