Skip to content

Restore component-level custom validation message overrides - #475

Merged
alexluckett merged 20 commits into
mainfrom
bugfix/custom-validation-message-override-tests
Oct 23, 2026
Merged

alexluckett merged 20 commits into
mainfrom
bugfix/custom-validation-message-override-tests

Conversation

@alexluckett

@alexluckett alexluckett commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

A v5 change stopped options.customValidationMessage and options.customValidationMessages from working for some components. This PR makes these options work again, and adds tests for each form component type to ensure it doesn't break again.

Architecture

ComponentCollection.validate() calls the getValidationMessagesOverride(translator) method of a component only when it gets a translator. A real request always gives a translator. The old unit tests did not give one, so they did not find the problem. Some components use this method to translate the default English messages. The method replaced all messages in the schema, so it removed the messages that the form author set. The new helper withCustomValidationOverrides() (components/helpers/index.ts) ensures that they are still supplied, even when a translator is provided.

Key changes

  • NumberField, LocationFieldBase (for OsGridRefField and NationalGridFieldNumberField), AutocompleteField and YesNoField use the new helper. Before, they always returned the translated defaults.
  • There are 2 new test files: customValidationMessage.test.ts for the singular option and customValidationMessages.test.ts for the map. Each file has a list of the types that support the option and a list of the types that do not.

…ides

Checks every component type against options.customValidationMessage(s),
with and without a translator. 5 components lose the override at request
time because getValidationMessagesOverride() always replaces the message.
…esent

getValidationMessagesOverride() always replaced Joi's messages with the
engine's translated defaults, so a form author's customValidationMessage(s)
was silently dropped on real requests, where a translator is always passed.
The author's message now wins for any key they set.
The keep-the-author's-message filter was duplicated across four
components. Moved it into withCustomValidationOverrides(), which also
holds the one type workaround for Joi's LanguageMessages type not
covering JoiExpression values, instead of repeating the cast per file.
…st coverage

withCustomValidationOverrides() now also skips translated defaults when a
single customValidationMessage is set, instead of each component repeating
that check. Added singular-message tests for the six components that
support it but had only plural coverage, confirming the fix against a
v4.23.0 baseline for both forms of the option.
withCustomValidationOverrides() now builds the override for every key in
the schema instead of returning null and relying on the constructor
having already set the same keys. This removes the implicit assumption
that the constructor's key list matches the translated defaults, and
callers no longer need to handle a null result.
Support for options.customValidationMessage (singular) is not the same
as support for options.customValidationMessages (plural): 3 components
support only the plural form, and 17 don't even declare the singular
field on their options type, so the two were being conflated in one
matrix. Each option now has its own file, its own supported/not-supported
lists, and its own coverage guard test.
Splitting the singular and plural test files left getCompositeFormData()
copy-pasted in both. Moved it to test/helpers/component-helpers.js,
next to getFormData(), so it exists once.
@alexluckett
alexluckett added this pull request to stack #477 October 5, 2026 14:06
TrackingField came in from main. Its options type declares
customValidationMessages through FormFieldBase but the component never
reads it, so it is listed as not supported for the plural option and as
having no singular option.
The coverage guard tests now compare against form component types only,
using isFormType from @defra/forms-model, so that content-only types are
classified in one place rather than repeated in each test file.
Each test file now runs one supported test and one not-supported test
for every type in its lists, rather than a hand-written block per
component. A fixture table holds only what differs by type: extra
definition fields and the empty input for composite fields.

The not-supported test also checks that the baseline has errors, so
that a wrong fixture cannot pass with no errors on either side.
Composite fields and GeospatialField give a validation error with empty
form data, so their fixture entries and the getCompositeFormData test
helper are removed.
The extra definition fields, inputs and model setup move to
test/helpers/validation-helpers.ts, so that both test files use one
copy. Each extra definition entry is typed as a partial of its own
component definition from @defra/forms-model, so that a field that does
not belong to the type is a type error. The helper is in test/ rather
than src/, so that the type build, which compiles only src/, does not
include test files.
An if/else-if chain without a final else is a Sonar code smell, so the
global override returns early and the defaults use a plain if.
@alexluckett
alexluckett marked this pull request as ready for review October 22, 2026 23:13
@alexluckett
alexluckett removed this pull request from stack #477 October 22, 2026 23:13

@jbarnsley10 jbarnsley10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Just that question about single message for Autocomplete, and there are two minor Sonar issues. Approving in principle

@alexluckett
alexluckett enabled auto-merge October 23, 2026 10:58
@sonarqubecloud

Copy link
Copy Markdown

@alexluckett
alexluckett merged commit adac383 into main Oct 23, 2026
23 checks passed
@alexluckett
alexluckett deleted the bugfix/custom-validation-message-override-tests branch October 23, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants