diff --git a/src/registry/fieldset/index.stories.ts b/src/registry/fieldset/index.stories.ts deleted file mode 100644 index f94f5f1..0000000 --- a/src/registry/fieldset/index.stories.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type {Meta, StoryObj} from '@storybook/react'; - -import FormioComponent from '@/components/FormioComponent'; -import {withFormik} from '@/sb-decorators'; - -import Fieldset from './'; - -export default { - title: 'Component registry / layout / fieldset', - component: Fieldset, - decorators: [withFormik], - args: { - renderNested: FormioComponent, - }, -} satisfies Meta; - -type Story = StoryObj; - -export const MinimalConfiguration: Story = { - args: { - componentDefinition: { - id: 'component1', - type: 'fieldset', - key: 'fieldset', - label: 'Fieldset label', - hideHeader: false, - components: [ - { - id: 'component2', - type: 'textfield', - key: 'my.textfield', - label: 'A simple textfield', - }, - ], - }, - }, - parameters: { - formik: { - initialValues: { - my: { - textfield: '', - }, - }, - }, - }, -}; diff --git a/src/registry/fieldset/index.stories.tsx b/src/registry/fieldset/index.stories.tsx new file mode 100644 index 0000000..fac2283 --- /dev/null +++ b/src/registry/fieldset/index.stories.tsx @@ -0,0 +1,126 @@ +import {AnyComponentSchema} from '@open-formulieren/types'; +import type {Meta, StoryObj} from '@storybook/react'; +import {expect, fn, userEvent, within} from '@storybook/test'; + +import FormioComponent from '@/components/FormioComponent'; +import FormioForm, {FormioFormProps} from '@/components/FormioForm'; +import {withFormik} from '@/sb-decorators'; + +import Fieldset from './'; + +export default { + title: 'Component registry / layout / fieldset', + component: Fieldset, + decorators: [withFormik], + args: { + renderNested: FormioComponent, + }, +} satisfies Meta; + +type Story = StoryObj; + +export const MinimalConfiguration: Story = { + args: { + componentDefinition: { + id: 'component1', + type: 'fieldset', + key: 'fieldset', + label: 'Fieldset label', + hideHeader: false, + components: [ + { + id: 'component2', + type: 'textfield', + key: 'my.textfield', + label: 'A simple textfield', + }, + ], + }, + }, + parameters: { + formik: { + initialValues: { + my: { + textfield: '', + }, + }, + }, + }, +}; + +interface ValidationStoryArgs { + nestedComponents: AnyComponentSchema[]; + onSubmit: FormioFormProps['onSubmit']; +} + +type ValidationStory = StoryObj; + +const BaseValidationStory: ValidationStory = { + render: args => ( + +
+ +
+
+ ), + parameters: { + formik: { + disable: true, + }, + }, +}; + +export const ValidatesNestedComponents: ValidationStory = { + ...BaseValidationStory, + args: { + onSubmit: fn(), + nestedComponents: [ + { + id: 'textfield', + key: 'textfield', + type: 'textfield', + label: 'A text field', + validate: { + maxLength: 3, + }, + }, + { + id: 'email', + key: 'email', + type: 'email', + label: 'Email address', + validateOn: 'blur', + validate: { + required: true, + }, + }, + ], + }, + play: async ({canvasElement, args}) => { + const canvas = within(canvasElement); + + const textField = canvas.getByLabelText('A text field'); + await userEvent.type(textField, 'too long'); + const emailField = canvas.getByLabelText('Email address'); + await userEvent.type(emailField, 'bad value'); + + await userEvent.click(canvas.getByRole('button', {name: 'Submit'})); + + expect(await canvas.findByText('String must contain at most 3 character(s)')).toBeVisible(); + expect(await canvas.findByText('Invalid email')).toBeVisible(); + + expect(args.onSubmit).not.toHaveBeenCalled(); + }, +}; diff --git a/src/registry/fieldset/index.tsx b/src/registry/fieldset/index.tsx index a9e46cc..2681dcd 100644 --- a/src/registry/fieldset/index.tsx +++ b/src/registry/fieldset/index.tsx @@ -30,3 +30,4 @@ const Fieldset: React.FC = ({ export default Fieldset; export {default as getInitialValues} from './initialValues'; +export {default as getValidationSchema} from './validationSchema'; diff --git a/src/registry/fieldset/validationSchema.ts b/src/registry/fieldset/validationSchema.ts new file mode 100644 index 0000000..f6b0de4 --- /dev/null +++ b/src/registry/fieldset/validationSchema.ts @@ -0,0 +1,22 @@ +import type {FieldsetComponentSchema} from '@open-formulieren/types'; + +import type {GetRegistryEntry, GetValidationSchema} from '@/registry/types'; +import type {SchemaRecord} from '@/validationSchema'; + +const getValidationSchema: GetValidationSchema = ( + {components}, + getRegistryEntry: GetRegistryEntry +) => { + const componentSchemas = components.reduce((acc: SchemaRecord, componentDefinition) => { + const getValidationSchema = getRegistryEntry(componentDefinition)?.getValidationSchema; + if (getValidationSchema !== undefined) { + const schemaRecord = getValidationSchema(componentDefinition, getRegistryEntry); + acc = {...acc, ...schemaRecord}; + } + return acc; + }, {} satisfies SchemaRecord); + + return componentSchemas; +}; + +export default getValidationSchema; diff --git a/src/registry/registry.ts b/src/registry/registry.ts index 588e9b3..e348770 100644 --- a/src/registry/registry.ts +++ b/src/registry/registry.ts @@ -4,7 +4,10 @@ import Email, { getInitialValues as emailGetInitialValues, getValidationSchema as emailGetValidationSchema, } from './email'; -import Fieldset, {getInitialValues as fieldsetGetInitialValues} from './fieldset'; +import Fieldset, { + getInitialValues as fieldsetGetInitialValues, + getValidationSchema as fieldsetGetValidationSchema, +} from './fieldset'; import TextField, { getInitialValues as textFieldGetInitialValues, getValidationSchema as textFieldGetValidationSchema, @@ -35,6 +38,7 @@ const REGISTRY: Registry = { fieldset: { formField: Fieldset, getInitialValues: fieldsetGetInitialValues, + getValidationSchema: fieldsetGetValidationSchema, }, // deprecated }; diff --git a/src/validationSchema.ts b/src/validationSchema.ts index d6a4281..a7bc56c 100644 --- a/src/validationSchema.ts +++ b/src/validationSchema.ts @@ -10,7 +10,7 @@ type SchemaTree = { [k: string]: z.ZodFirstPartySchemaTypes | SchemaTree; }; -type SchemaRecord = Record; +export type SchemaRecord = Record; /** * Process key-value pairs where keys are Formio component keys that may have dots in