Repository navigation
Restore component-level custom validation message overrides - #475
Draft
alexluckett wants to merge 7 commits into
Draft
alexluckett wants to merge 7 commits into
alexluckett wants to merge 7 commits into
Conversation
…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
added this pull request to stack #477
October 5, 2026 14:06
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sgetValidationMessagesOverride(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-suppliedcustomValidationMessage(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(coversOsGridRefFieldandNationalGridFieldNumberField),AutocompleteFieldandYesNoFieldnow call the shared helper instead of unconditionally returning translated defaults.withCustomValidationOverrides()helper centralises the "author wins" logic, including the singularcustomValidationMessagecase, so it is not duplicated across components.customValidationMessage.test.tschecksoptions.customValidationMessage(s)for everyComponentType, both with and without a translator, plus a guard test that fails if a newComponentTypeis added without being classified as supported, not supported, or content-only.YesNoFieldnever 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 singularcustomValidationMessageoption, so this PR keeps that pattern for consistency. Switching the whole codebase to the wildcard would be a separate, broader change.