Skip to content

Commit

Permalink
✨ Ensure fieldsets produce validation schemas
Browse files Browse the repository at this point in the history
Fieldsets need to yield the validation schemas of the
components contained within the fieldset. This requires
wiring up the checks at the component level.
  • Loading branch information
sergei-maertens committed Jan 11, 2025
1 parent 15ee762 commit f4eed48
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 48 deletions.
46 changes: 0 additions & 46 deletions src/registry/fieldset/index.stories.ts

This file was deleted.

126 changes: 126 additions & 0 deletions src/registry/fieldset/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Fieldset>;

type Story = StoryObj<typeof Fieldset>;

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<ValidationStoryArgs>;

const BaseValidationStory: ValidationStory = {
render: args => (
<FormioForm
onSubmit={args.onSubmit}
components={[
{
id: 'fieldset',
key: 'fieldset',
type: 'fieldset',
label: 'My group of fields',
hideHeader: false,
components: args.nestedComponents,
},
]}
>
<div style={{marginBlockStart: '20px'}}>
<button type="submit">Submit</button>
</div>
</FormioForm>
),
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();
},
};
1 change: 1 addition & 0 deletions src/registry/fieldset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ const Fieldset: React.FC<FieldsetProps> = ({

export default Fieldset;
export {default as getInitialValues} from './initialValues';
export {default as getValidationSchema} from './validationSchema';
22 changes: 22 additions & 0 deletions src/registry/fieldset/validationSchema.ts
Original file line number Diff line number Diff line change
@@ -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<FieldsetComponentSchema> = (
{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;
6 changes: 5 additions & 1 deletion src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -35,6 +38,7 @@ const REGISTRY: Registry = {
fieldset: {
formField: Fieldset,
getInitialValues: fieldsetGetInitialValues,
getValidationSchema: fieldsetGetValidationSchema,
},
// deprecated
};
2 changes: 1 addition & 1 deletion src/validationSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type SchemaTree = {
[k: string]: z.ZodFirstPartySchemaTypes | SchemaTree;
};

type SchemaRecord = Record<string, z.ZodFirstPartySchemaTypes>;
export type SchemaRecord = Record<string, z.ZodFirstPartySchemaTypes>;

/**
* Process key-value pairs where keys are Formio component keys that may have dots in
Expand Down

0 comments on commit f4eed48

Please sign in to comment.