Skip to content

Restore component-level custom validation message overrides - #475

Draft
alexluckett wants to merge 7 commits into
mainfrom
bugfix/custom-validation-message-override-tests
Draft

alexluckett wants to merge 7 commits into
mainfrom
bugfix/custom-validation-message-override-tests

Conversation

@alexluckett

Copy link
Copy Markdown
Contributor

A v5 change silently broke options.customValidationMessage(s) for several components. This PR restores it and adds test coverage that proves it for every component type.

Architecture

ComponentCollection.validate() only calls a component's getValidationMessagesOverride(translator) when a translator is passed. A real request always passes a translator; older unit tests never did, so the break was invisible to the existing test suite. Several components override this hook to translate the engine's default English messages. The hook always replaced the schema's messages wholesale, so it discarded any author-supplied customValidationMessage(s) at request time. A shared helper, withCustomValidationOverrides() (components/helpers/index.ts), now builds the message for each schema key: the author's message wins, translated defaults fill any key the author left unset.

Key changes

  • NumberField, LocationFieldBase (covers OsGridRefField and NationalGridFieldNumberField), AutocompleteField and YesNoField now call the shared helper instead of unconditionally returning translated defaults.
  • New withCustomValidationOverrides() helper centralises the "author wins" logic, including the singular customValidationMessage case, so it is not duplicated across components.
  • New customValidationMessage.test.ts checks options.customValidationMessage(s) for every ComponentType, both with and without a translator, plus a guard test that fails if a new ComponentType is added without being classified as supported, not supported, or content-only.
  • The support matrix comes from reading each component's source, and is confirmed against the last v4 release (v4.23.0) to separate a real regression from behaviour that never worked. YesNoField never read the option in v4, so its case is not a regression.

Out of scope

Joi's message resolution supports a * wildcard key that catches any error code without enumerating one. The existing constructors already enumerate keys explicitly for the singular customValidationMessage option, so this PR keeps that pattern for consistency. Switching the whole codebase to the wildcard would be a separate, broader change.

…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

This branch has not been deployed

No deployments
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.

1 participant