Skip to content

Commit

Permalink
fix: subfield errors should be consider validated based on its parent (
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Apr 28, 2024
1 parent 3924d70 commit 1a11a27
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 10 deletions.
7 changes: 6 additions & 1 deletion packages/conform-dom/form.ts
Expand Up @@ -410,9 +410,14 @@ function handleIntent<Error>(
}
}

const validatedFields = fields?.filter((name) => meta.validated[name]) ?? [];

meta.error = Object.entries(meta.error).reduce<Record<string, Error>>(
(result, [name, error]) => {
if (meta.validated[name]) {
if (
meta.validated[name] ||
validatedFields.some((field) => isPrefix(name, field))
) {
result[name] = error;
}

Expand Down
18 changes: 15 additions & 3 deletions playground/app/components.tsx
Expand Up @@ -80,10 +80,22 @@ interface FieldProps {
label: string;
inline?: boolean;
meta?: FieldMetadata<any, any>;
allErrors?: boolean;
children: ReactNode;
}

export function Field({ label, inline, meta, children }: FieldProps) {
export function Field({
label,
inline,
meta,
allErrors,
children,
}: FieldProps) {
const errors =
meta && allErrors
? Object.values(meta.allErrors).flat()
: meta?.errors ?? [];

return (
<div className="mb-4">
<div
Expand All @@ -102,10 +114,10 @@ export function Field({ label, inline, meta, children }: FieldProps) {
{children}
</div>
<div id={meta?.errorId} className="my-1 space-y-0.5">
{!meta?.errors?.length ? (
{errors.length === 0 ? (
<p className="text-pink-600 text-sm" />
) : (
meta.errors.map((message) => (
errors.map((message) => (
<p className="text-pink-600 text-sm" key={message}>
{message}
</p>
Expand Down
17 changes: 15 additions & 2 deletions playground/app/routes/collection.tsx
Expand Up @@ -7,7 +7,20 @@ import { Playground, Field } from '~/components';

const schema = z.object({
singleChoice: z.string({ required_error: 'Required' }),
multipleChoice: z.string().array().min(1, 'Required'),
multipleChoice: z
.enum(['a', 'b', 'c'], {
errorMap(issue, ctx) {
if (issue.code === 'invalid_enum_value') {
return { message: 'Invalid' };
}

return {
message: ctx.defaultError,
};
},
})
.array()
.min(1, 'Required'),
});

export async function loader({ request }: LoaderArgs) {
Expand Down Expand Up @@ -51,7 +64,7 @@ export default function Example() {
</label>
))}
</Field>
<Field label="Multiple choice" meta={fields.multipleChoice}>
<Field label="Multiple choice" meta={fields.multipleChoice} allErrors>
{getCollectionProps(fields.multipleChoice, {
type: 'checkbox',
options: ['a', 'b', 'c', 'd'],
Expand Down
28 changes: 24 additions & 4 deletions tests/integrations/collection.spec.ts
@@ -1,7 +1,12 @@
import { type Page, test, expect } from '@playwright/test';
import { getPlayground } from '../helpers';

async function runValidationScenario(page: Page) {
async function runTest(
page: Page,
options: {
noClientValidate?: boolean;
} = {},
) {
const playground = getPlayground(page);

await playground.submit.click();
Expand All @@ -13,6 +18,17 @@ async function runValidationScenario(page: Page) {

await expect(playground.error).toHaveText(['', 'Required']);

if (!options.noClientValidate) {
const invalidOption = playground.container.getByLabel('D');

await invalidOption.click();

await expect(playground.error).toHaveText(['', 'Invalid']);

// Uncheck it to remove the error
await invalidOption.click();
}

await playground.container.getByLabel('C').click();
await playground.submit.click();

Expand Down Expand Up @@ -43,12 +59,14 @@ async function runValidationScenario(page: Page) {
test.describe('With JS', () => {
test('Client Validation', async ({ page }) => {
await page.goto('/collection');
await runValidationScenario(page);
await runTest(page);
});

test('Server Validation', async ({ page }) => {
await page.goto('/collection?noClientValidate=yes');
await runValidationScenario(page);
await runTest(page, {
noClientValidate: true,
});
});

test('Form reset', async ({ page }) => {
Expand All @@ -69,6 +87,8 @@ test.describe('No JS', () => {

test('Validation', async ({ page }) => {
await page.goto('/collection');
await runValidationScenario(page);
await runTest(page, {
noClientValidate: true,
});
});
});

0 comments on commit 1a11a27

Please sign in to comment.