-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🦺 [#52] Implement and test input validation
When specifying values manually, the form builder enforces accessible practices in the value/label of each option. There are some limitations in the flexibility with zod validation unions and deep discriminators, but the UI currently prevents ending up in those edge case.
- Loading branch information
1 parent
9ff5333
commit 8233fd2
Showing
3 changed files
with
123 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import {IntlShape} from 'react-intl'; | ||
import {z} from 'zod'; | ||
|
||
import {buildCommonSchema, jsonSchema, optionSchema} from '@/registry/validation'; | ||
|
||
// z.object(...).or(z.object(...)) based on openForms.dataSrc doesn't seem to work, | ||
// looks like the union validation only works if the discriminator is in the top level | ||
// object :( | ||
// so we mark each aspect as optional so that *when* it is provided, we can run the | ||
// validation | ||
const buildValuesSchema = (intl: IntlShape) => | ||
z.object({ | ||
values: optionSchema(intl).array().min(1).optional(), | ||
openForms: z.object({ | ||
dataSrc: z.union([z.literal('manual'), z.literal('variable')]), | ||
// TODO: wire up infernologic type checking | ||
itemsExpression: jsonSchema.optional(), | ||
}), | ||
}); | ||
|
||
const schema = (intl: IntlShape) => buildCommonSchema(intl).and(buildValuesSchema(intl)); | ||
|
||
export default schema; |
60 changes: 60 additions & 0 deletions
60
src/registry/selectboxes/selectboxes-validation.stories.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import {expect} from '@storybook/jest'; | ||
import {Meta, StoryObj} from '@storybook/react'; | ||
import {userEvent, within} from '@storybook/testing-library'; | ||
|
||
import ComponentEditForm from '@/components/ComponentEditForm'; | ||
import {BuilderContextDecorator} from '@/sb-decorators'; | ||
|
||
export default { | ||
title: 'Builder components/SelectBoxes/Validations', | ||
component: ComponentEditForm, | ||
decorators: [BuilderContextDecorator], | ||
parameters: { | ||
builder: {enableContext: true}, | ||
}, | ||
args: { | ||
isNew: true, | ||
component: { | ||
id: 'wqimsadk', | ||
type: 'selectboxes', | ||
key: 'selectboxes', | ||
label: 'A selectboxes field', | ||
openForms: { | ||
dataSrc: 'manual', | ||
translations: {}, | ||
}, | ||
values: [{value: '', label: ''}], | ||
defaultValue: {}, | ||
}, | ||
|
||
builderInfo: { | ||
title: 'Select Boxes', | ||
icon: 'plus-square', | ||
group: 'basic', | ||
weight: 60, | ||
schema: {}, | ||
}, | ||
}, | ||
} as Meta<typeof ComponentEditForm>; | ||
|
||
type Story = StoryObj<typeof ComponentEditForm>; | ||
|
||
export const ManualMinimumOneValue: Story = { | ||
name: 'Manual values: must have at least one non-empty value', | ||
play: async ({canvasElement, step}) => { | ||
const canvas = within(canvasElement); | ||
|
||
await step('Option values and labels are required fields', async () => { | ||
// a value must be set, otherwise there's nothing to check and a label must be | ||
// set, otherwise there is no clickable/accessible label for an option. | ||
|
||
// we trigger input validation by touching the field and clearing it again | ||
const labelInput = canvas.getByLabelText('Option label'); | ||
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(); | ||
}); | ||
}, | ||
}; |
This file contains 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