From cf82e1e3fbd719849d6c22d51b9d921fe1dda5cf Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 19 Sep 2024 10:59:41 +0200 Subject: [PATCH 1/4] :test_tube: [open-formulieren/open-forms#4637] added tests to validate option translations --- .../ComponentConfiguration.stories.tsx | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/components/ComponentConfiguration.stories.tsx b/src/components/ComponentConfiguration.stories.tsx index 94a7679f..2f9396d8 100644 --- a/src/components/ComponentConfiguration.stories.tsx +++ b/src/components/ComponentConfiguration.stories.tsx @@ -1204,6 +1204,21 @@ export const SelectBoxes: Story = { // check that the option labels are in the translations table expect(await editForm.findByText('Option label 1')).toBeVisible(); expect(await editForm.findByText('Second option')).toBeVisible(); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await userEvent.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await userEvent.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); }); await step('Set up itemsExpression for options', async () => { @@ -1239,7 +1254,9 @@ export const SelectBoxes: Story = { openForms: { dataSrc: 'variable', itemsExpression: {var: 'someVar'}, - translations: {}, + translations: { + nl: {description: '', label: '', tooltip: ''}, + }, }, defaultValue: {}, // Advanced tab @@ -1410,6 +1427,21 @@ export const Radio: Story = { // check that the option labels are in the translations table expect(await editForm.findByText('Option label 1')).toBeVisible(); expect(await editForm.findByText('Second option')).toBeVisible(); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await userEvent.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await userEvent.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); }); await step('Set up itemsExpression for options', async () => { @@ -1445,7 +1477,9 @@ export const Radio: Story = { openForms: { dataSrc: 'variable', itemsExpression: {var: 'someVar'}, - translations: {}, + translations: { + nl: {description: '', label: '', tooltip: ''}, + }, }, defaultValue: '', // Advanced tab @@ -1623,6 +1657,21 @@ export const Select: Story = { // check that the option labels are in the translations table expect(await editForm.findByText('Option label 1')).toBeVisible(); expect(await editForm.findByText('Second option')).toBeVisible(); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await userEvent.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await userEvent.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); }); await step('Set up itemsExpression for options', async () => { @@ -1665,7 +1714,9 @@ export const Select: Story = { openForms: { dataSrc: 'variable', itemsExpression: {var: 'someVar'}, - translations: {}, + translations: { + nl: {description: '', label: '', tooltip: ''}, + }, }, defaultValue: '', // Advanced tab From 1c748fa4dd5b5a485e7c5307ab15f1f9d72cad93 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 19 Sep 2024 12:38:18 +0200 Subject: [PATCH 2/4] :bug: [open-formulieren/open-forms#4637] Ensure value translations are optional --- src/registry/validation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registry/validation.ts b/src/registry/validation.ts index 4cd5c5b3..95f7bb1b 100644 --- a/src/registry/validation.ts +++ b/src/registry/validation.ts @@ -84,7 +84,7 @@ export const jsonSchema: z.ZodType = z.lazy(() => // Maps to @open-formulieren/types common.ts Option type. const optionTranslationSchema = z.object({ - label: z.string(), + label: z.string().optional(), }); export const optionSchema = (intl: IntlShape) => @@ -109,7 +109,7 @@ export const optionSchema = (intl: IntlShape) => .object({ // zod doesn't seem to be able to use our supportedLanguageCodes for z.object keys, // they need to be defined statically. So, 'record' it is. - translations: z.record(optionTranslationSchema.optional()), + translations: z.record(z.string(), optionTranslationSchema.optional()), }) .optional(), }); From 6d76da58411ebc69be0c22daf04cdde7bd1fea7f Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 19 Sep 2024 12:56:13 +0200 Subject: [PATCH 3/4] :white_check_mark: [open-formulieren/open-forms#4637] Added translation tests to radio, select and selectboxes component tests --- .../radio/radio-validation.stories.ts | 27 +++++++++ .../select/select-validation.spec.tsx | 57 +++++++++++++++++++ .../selectboxes-validation.stories.ts | 27 +++++++++ 3 files changed, 111 insertions(+) diff --git a/src/registry/radio/radio-validation.stories.ts b/src/registry/radio/radio-validation.stories.ts index 2ea45586..89ab87e8 100644 --- a/src/registry/radio/radio-validation.stories.ts +++ b/src/registry/radio/radio-validation.stories.ts @@ -57,3 +57,30 @@ export const ManualMinimumOneValue: Story = { }); }, }; + +export const TranslationsArentRequired: Story = { + name: 'Translations: translations aren\'t required fields', + play: async ({canvasElement, step}) => { + const canvas = within(canvasElement); + + await step('Translations aren\'t required fields', async () => { + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + const editForm = within(canvas.getByTestId('componentEditForm')); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await userEvent.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await userEvent.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); + }); + }, +}; diff --git a/src/registry/select/select-validation.spec.tsx b/src/registry/select/select-validation.spec.tsx index 28e2bdcd..54a5619e 100644 --- a/src/registry/select/select-validation.spec.tsx +++ b/src/registry/select/select-validation.spec.tsx @@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event'; import ComponentEditForm from '@/components/ComponentEditForm'; import {contextRender, screen} from '@/tests/test-utils'; +import {expect, within} from '@storybook/test'; beforeAll(() => { jest.useFakeTimers(); @@ -96,3 +97,59 @@ test('There is always at least one option', async () => { const firstValueLabel = await screen.findByTestId('input-data.values[0].label'); expect(firstValueLabel).toBeVisible(); }); + +test('All translations are optional', async () => { + const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime}); + const onSubmit = jest.fn(); + const component = { + id: 'wqimsadk', + type: 'select', + key: 'select', + label: 'A select field', + dataSrc: 'values', + openForms: { + dataSrc: 'manual', + translations: {}, + }, + data: { + values: [], + }, + defaultValue: '', + } satisfies SelectComponentSchema; + + const builderInfo = { + title: 'Select', + icon: 'th-list', + group: 'basic', + weight: 70, + schema: {}, + }; + contextRender( + + ); + + await user.click(screen.getByRole('tab', {name: 'Translations'})); + const editForm = within(screen.getByTestId('componentEditForm')); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await user.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await user.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await user.click(screen.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); +}); diff --git a/src/registry/selectboxes/selectboxes-validation.stories.ts b/src/registry/selectboxes/selectboxes-validation.stories.ts index 5af78ed1..a8cd3443 100644 --- a/src/registry/selectboxes/selectboxes-validation.stories.ts +++ b/src/registry/selectboxes/selectboxes-validation.stories.ts @@ -72,6 +72,33 @@ export const ManualMinimumOneValue: Story = { }, }; +export const TranslationsArentRequired: Story = { + name: 'Translations: translations aren\'t required fields', + play: async ({canvasElement, step}) => { + const canvas = within(canvasElement); + + await step('Translations aren\'t required fields', async () => { + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + const editForm = within(canvas.getByTestId('componentEditForm')); + + // Check that all translations can be filled + const inputs = editForm.getAllByRole('textbox'); + for (let input of inputs) { + await userEvent.type(input, 'manualTranslation'); + await expect(input).toHaveValue('manualTranslation'); + await userEvent.clear(input); + await expect(input).toHaveValue(''); + } + + // Removing focus from the last input + await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); + + // Check that none of the inputs have a Required error message + await expect(await editForm.queryByText('Required')).toBeNull(); + }); + }, +}; + export const MinAndMaxSelectedInitialValues: Story = { name: 'Initial min and max count', args: { From 3a98100aa2b24464e98965d7f685e948dcecb0b7 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 19 Sep 2024 14:25:17 +0200 Subject: [PATCH 4/4] :white_check_mark: [open-formulieren/open-forms#4637] Fixed some storybook tests and prettified code --- src/components/ComponentConfiguration.stories.tsx | 12 +++++++----- src/registry/addressNL/edit.stories.ts | 6 ++++++ src/registry/radio/radio-validation.stories.ts | 15 ++++++++------- src/registry/select/select-validation.spec.tsx | 2 +- .../selectboxes/selectboxes-validation.stories.ts | 4 ++-- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/components/ComponentConfiguration.stories.tsx b/src/components/ComponentConfiguration.stories.tsx index 2f9396d8..6c2e0b7e 100644 --- a/src/components/ComponentConfiguration.stories.tsx +++ b/src/components/ComponentConfiguration.stories.tsx @@ -974,8 +974,10 @@ export const FileUpload: Story = { await step('File tab', async () => { await userEvent.click(canvas.getByRole('link', {name: 'File'})); - await expect(canvas.getByLabelText('Maximum file size')).toHaveDisplayValue('10MB'); - await expect(canvas.getByText('Note that the server upload limit is 50MB.')).toBeVisible(); + await waitFor(async () => { + await expect(canvas.getByLabelText('Maximum file size')).toHaveDisplayValue('10MB'); + await expect(canvas.getByText('Note that the server upload limit is 50MB.')).toBeVisible(); + }); // check that the file types are visible canvas.getByLabelText('File types').focus(); @@ -1209,16 +1211,16 @@ export const SelectBoxes: Story = { const inputs = editForm.getAllByRole('textbox'); for (let input of inputs) { await userEvent.type(input, 'manualTranslation'); - await expect(input).toHaveValue('manualTranslation'); + expect(input).toHaveValue('manualTranslation'); await userEvent.clear(input); - await expect(input).toHaveValue(''); + expect(input).toHaveValue(''); } // Removing focus from the last input await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); // Check that none of the inputs have a Required error message - await expect(await editForm.queryByText('Required')).toBeNull(); + expect(await editForm.queryByText('Required')).toBeNull(); }); await step('Set up itemsExpression for options', async () => { diff --git a/src/registry/addressNL/edit.stories.ts b/src/registry/addressNL/edit.stories.ts index 65b61376..3452c4a6 100644 --- a/src/registry/addressNL/edit.stories.ts +++ b/src/registry/addressNL/edit.stories.ts @@ -35,10 +35,12 @@ 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]); + 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(); + const errorMessageInput = await canvas.findByLabelText('Error message for "pattern"'); expect(errorMessageInput).toHaveDisplayValue(''); }); @@ -53,10 +55,12 @@ 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]); + 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(); + const errorMessageInput = await canvas.findByLabelText('Error message for "pattern"'); expect(errorMessageInput).toHaveDisplayValue(''); }); @@ -93,6 +97,7 @@ export const PostcodeValidationTabWithConfiguration: Story = { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('Postcode')[0]); const patternInput = await canvas.findByLabelText('Regular expression for postcode'); + expect(patternInput).toBeVisible(); expect(patternInput).toHaveValue('1017 [A-Za-z]{2}'); @@ -136,6 +141,7 @@ export const CityValidationTabWithConfiguration: Story = { await userEvent.click(canvas.getByRole('link', {name: 'Validation'})); await userEvent.click(canvas.getAllByText('City')[0]); const patternInput = await canvas.findByLabelText('Regular expression for city'); + expect(patternInput).toBeVisible(); expect(patternInput).toHaveValue('Amsterdam'); diff --git a/src/registry/radio/radio-validation.stories.ts b/src/registry/radio/radio-validation.stories.ts index 89ab87e8..e883827d 100644 --- a/src/registry/radio/radio-validation.stories.ts +++ b/src/registry/radio/radio-validation.stories.ts @@ -52,18 +52,19 @@ export const ManualMinimumOneValue: Story = { await userEvent.type(labelInput, 'Foo'); await userEvent.clear(labelInput); await userEvent.keyboard('[Tab]'); - await expect(await canvas.findByText('The option label is a required field.')).toBeVisible(); - await expect(await canvas.findByText('The option value is a required field.')).toBeVisible(); + + expect(await canvas.findByText('The option label is a required field.')).toBeVisible(); + expect(await canvas.findByText('The option value is a required field.')).toBeVisible(); }); }, }; export const TranslationsArentRequired: Story = { - name: 'Translations: translations aren\'t required fields', + name: "Translations: translations aren't required fields", play: async ({canvasElement, step}) => { const canvas = within(canvasElement); - await step('Translations aren\'t required fields', async () => { + await step("Translations aren't required fields", async () => { await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); const editForm = within(canvas.getByTestId('componentEditForm')); @@ -71,16 +72,16 @@ export const TranslationsArentRequired: Story = { const inputs = editForm.getAllByRole('textbox'); for (let input of inputs) { await userEvent.type(input, 'manualTranslation'); - await expect(input).toHaveValue('manualTranslation'); + expect(input).toHaveValue('manualTranslation'); await userEvent.clear(input); - await expect(input).toHaveValue(''); + expect(input).toHaveValue(''); } // Removing focus from the last input await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); // Check that none of the inputs have a Required error message - await expect(await editForm.queryByText('Required')).toBeNull(); + expect(await editForm.queryByText('Required')).toBeNull(); }); }, }; diff --git a/src/registry/select/select-validation.spec.tsx b/src/registry/select/select-validation.spec.tsx index 54a5619e..2068cd30 100644 --- a/src/registry/select/select-validation.spec.tsx +++ b/src/registry/select/select-validation.spec.tsx @@ -1,9 +1,9 @@ import {SelectComponentSchema} from '@open-formulieren/types'; +import {expect, within} from '@storybook/test'; import userEvent from '@testing-library/user-event'; import ComponentEditForm from '@/components/ComponentEditForm'; import {contextRender, screen} from '@/tests/test-utils'; -import {expect, within} from '@storybook/test'; beforeAll(() => { jest.useFakeTimers(); diff --git a/src/registry/selectboxes/selectboxes-validation.stories.ts b/src/registry/selectboxes/selectboxes-validation.stories.ts index a8cd3443..75223e24 100644 --- a/src/registry/selectboxes/selectboxes-validation.stories.ts +++ b/src/registry/selectboxes/selectboxes-validation.stories.ts @@ -73,11 +73,11 @@ export const ManualMinimumOneValue: Story = { }; export const TranslationsArentRequired: Story = { - name: 'Translations: translations aren\'t required fields', + name: "Translations: translations aren't required fields", play: async ({canvasElement, step}) => { const canvas = within(canvasElement); - await step('Translations aren\'t required fields', async () => { + await step("Translations aren't required fields", async () => { await userEvent.click(canvas.getByRole('tab', {name: 'Translations'})); const editForm = within(canvas.getByTestId('componentEditForm'));