diff --git a/src/components/builder/validate/i18n.tsx b/src/components/builder/validate/i18n.tsx index 9f87ca36..a19de2a5 100644 --- a/src/components/builder/validate/i18n.tsx +++ b/src/components/builder/validate/i18n.tsx @@ -2,7 +2,7 @@ import {PossibleValidatorErrorKeys, SchemaWithValidation} from '@open-formuliere import {useField} from 'formik'; import {isEqual} from 'lodash'; import {useContext, useEffect} from 'react'; -import {FormattedMessage, useIntl} from 'react-intl'; +import {FormattedMessage, defineMessage, useIntl} from 'react-intl'; import {BuilderContext} from '@/context'; @@ -10,10 +10,11 @@ import {DataMap, Panel, Tab, TabList, TabPanel, Tabs, TextField} from '../../for export function useManageValidatorsTranslations( keys: PossibleValidatorErrorKeys[], - field: string = 'translatedErrors' + prefix: string = '' ): void { + const fieldName = `${prefix}${prefix ? '.' : ''}translatedErrors`; const {supportedLanguageCodes} = useContext(BuilderContext); - const [{value}, , {setValue}] = useField(field); + const [{value}, , {setValue}] = useField(fieldName); useEffect(() => { const newValue = value @@ -68,6 +69,10 @@ const ValidationErrorTranslations = () => { defaultMessage="Error code" /> } + ariaLabelMessage={defineMessage({ + description: 'Accessible label for error message input field', + defaultMessage: 'Error message for "{key}"', + })} valueComponent={ = ({name, valueComponent, keyLabel = 'Key'}) => { +export const DataMap: React.FC = ({ + name, + valueComponent, + ariaLabelMessage, + keyLabel = 'Key', +}) => { + const intl = useIntl(); const [{value}, , {setValue}] = useField(name); const transformedValue = Object.entries(value).map(([key, value]) => ({key, value})); const columns = [keyLabel, valueComponent.props.label]; @@ -30,6 +38,9 @@ export const DataMap: React.FC = ({name, valueComponent, keyLabel const newValue = {...value, [item.key]: event.target.value}; setValue(newValue); }, + 'aria-label': ariaLabelMessage + ? intl.formatMessage(ariaLabelMessage, {key: item.key}) + : undefined, })} ))} diff --git a/src/registry/addressNL/edit.stories.ts b/src/registry/addressNL/edit.stories.ts index 8355648d..266b3724 100644 --- a/src/registry/addressNL/edit.stories.ts +++ b/src/registry/addressNL/edit.stories.ts @@ -35,13 +35,11 @@ export const PostcodeValidationTabWithoutConfiguration: Story = { await step('Navigate to validation tab and open Postcode validation', async () => { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('Postcode')[0]); - await waitFor(async () => { - expect(await canvas.findByText('Regular expression for postcode')).toBeVisible(); - expect(await canvas.findByText('NL')).toBeVisible(); - expect(await canvas.findByText('EN')).toBeVisible(); - expect(await canvas.findByText('Error code')).toBeVisible(); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - }); + expect(await canvas.findByLabelText('Regular expression for postcode')).toBeVisible(); + expect(await canvas.findByText('NL')).toBeVisible(); + expect(await canvas.findByText('EN')).toBeVisible(); + expect(await canvas.findByText('Error code')).toBeVisible(); + expect(canvas.getByLabelText('Error message for "pattern"')).toHaveDisplayValue(''); }); }, }; @@ -54,13 +52,11 @@ export const CityValidationTabWithoutConfiguration: Story = { await step('Navigate to validation tab and open City validation', async () => { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('City')[0]); - await waitFor(async () => { - expect(await canvas.findByText('Regular expression for city')).toBeVisible(); - expect(await canvas.findByText('NL')).toBeVisible(); - expect(await canvas.findByText('EN')).toBeVisible(); - expect(await canvas.findByText('Error code')).toBeVisible(); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - }); + expect(await canvas.findByLabelText('Regular expression for city')).toBeVisible(); + expect(await canvas.findByText('NL')).toBeVisible(); + expect(await canvas.findByText('EN')).toBeVisible(); + expect(await canvas.findByText('Error code')).toBeVisible(); + expect(canvas.getByLabelText('Error message for "pattern"')).toHaveDisplayValue(''); }); }, }; @@ -94,26 +90,23 @@ export const PostcodeValidationTabWithConfiguration: Story = { await step('Navigate to validation tab and open Postcode validation', async () => { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('Postcode')[0]); - await waitFor(async () => { - const patternInput = canvas.getByLabelText( - 'Regular expression for postcode' - ) as HTMLInputElement; - expect(patternInput).toBeVisible(); - expect(patternInput.value).toBe('1017 [A-Za-z]{2}'); + const patternInput = canvas.getByLabelText('Regular expression for postcode'); + expect(patternInput).toBeVisible(); + expect(patternInput).toHaveValue('1017 [A-Za-z]{2}'); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - expect(await canvas.findByDisplayValue('Postcode moet 1017 XX zijn')).toBeVisible(); + expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); + expect(await canvas.findByDisplayValue('Postcode moet 1017 XX zijn')).toBeVisible(); - await userEvent.click(await canvas.findByText('EN')); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - expect(await canvas.findByDisplayValue('Postal code must be 1017 XX')).toBeVisible(); - }); + await userEvent.click(await canvas.findByText('EN')); + expect(canvas.getByLabelText('Error message for "pattern"')).toHaveDisplayValue( + 'Postal code must be 1017 XX' + ); }); }, }; export const CityValidationTabWithConfiguration: Story = { - name: 'AddressNL postcode validation tab (with prior configuration)', + name: 'AddressNL city validation tab (with prior configuration)', args: { component: { id: 'wekruya', @@ -141,20 +134,17 @@ export const CityValidationTabWithConfiguration: Story = { await step('Navigate to validation tab and open City validation', async () => { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('City')[0]); - await waitFor(async () => { - const patternInput = canvas.getByLabelText( - 'Regular expression for city' - ) as HTMLInputElement; - expect(patternInput).toBeVisible(); - expect(patternInput.value).toBe('Amsterdam'); + const patternInput = canvas.getByLabelText('Regular expression for city'); + expect(patternInput).toBeVisible(); + expect(patternInput).toHaveValue('Amsterdam'); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - expect(await canvas.findByDisplayValue('De stad moet Amsterdam zijn')).toBeVisible(); + expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); + expect(await canvas.findByDisplayValue('De stad moet Amsterdam zijn')).toBeVisible(); - await userEvent.click(await canvas.findByText('EN')); - expect(await canvas.findByDisplayValue('pattern')).toBeVisible(); - expect(await canvas.findByDisplayValue('The city must be Amsterdam')).toBeVisible(); - }); + await userEvent.click(await canvas.findByText('EN')); + expect(canvas.getByLabelText('Error message for "pattern"')).toHaveDisplayValue( + 'The city must be Amsterdam' + ); }); }, }; diff --git a/src/registry/addressNL/edit.tsx b/src/registry/addressNL/edit.tsx index e572f840..ae046248 100644 --- a/src/registry/addressNL/edit.tsx +++ b/src/registry/addressNL/edit.tsx @@ -1,7 +1,7 @@ import {AddressNLComponentSchema} from '@open-formulieren/types'; import {TextField} from 'components/formio'; import {useContext} from 'react'; -import {FormattedMessage, useIntl} from 'react-intl'; +import {FormattedMessage, defineMessage, useIntl} from 'react-intl'; import { BuilderTabs, @@ -38,6 +38,7 @@ type PostcodeSchema = AddressSubComponents['postcode']; type CitySchema = AddressSubComponents['city']; export interface SubcomponentValidationProps { + prefix: string; component: keyof AddressSubComponents; label: React.ReactNode; tooltip: string; @@ -45,6 +46,7 @@ export interface SubcomponentValidationProps { } export const SubcomponentValidation: React.FC = ({ + prefix, component, label, tooltip, @@ -54,7 +56,7 @@ export const SubcomponentValidation: React.FC = ({ return ( <> = ({ {supportedLanguageCodes.map(code => ( } + ariaLabelMessage={defineMessage({ + description: 'Accessible label for error message input field', + defaultMessage: 'Error message for "{key}"', + })} valueComponent={ = () => { Validate.useManageValidatorsTranslations( ['pattern'], - `openForms.components.postcode.translatedErrors` - ); - Validate.useManageValidatorsTranslations( - ['pattern'], - `openForms.components.city.translatedErrors` + `openForms.components.postcode` ); + Validate.useManageValidatorsTranslations(['pattern'], `openForms.components.city`); return ( @@ -225,6 +228,7 @@ const EditForm: EditFormDefinition = () => { initialCollapsed > = () => { initialCollapsed >