From 3f81f5ffc835024cf3cc3d15f5503ed6876a462a Mon Sep 17 00:00:00 2001 From: Mohsen Madani Date: Wed, 9 Apr 2025 13:52:20 +0330 Subject: [PATCH] feat: add multiple errors --- src/utils/createSchemaFieldRule.spec.ts | 6 +++--- src/utils/formatErrors.spec.ts | 20 +++++++++++++++++--- src/utils/formatErrors.ts | 10 +++++++--- src/utils/validateFields.spec.ts | 12 ++++++------ src/utils/validateFields.ts | 4 ++-- stories/basic.stories.tsx | 4 ++++ 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/utils/createSchemaFieldRule.spec.ts b/src/utils/createSchemaFieldRule.spec.ts index 6a1f67e..9defcce 100644 --- a/src/utils/createSchemaFieldRule.spec.ts +++ b/src/utils/createSchemaFieldRule.spec.ts @@ -28,7 +28,7 @@ describe("createSchemaFieldValidator", () => { await expect( rule(formInstance).validator?.(fieldRule, {}, () => {}), - ).rejects.toEqual("Required"); + ).rejects.toEqual(["Required"]); }); it("should validate successfully NestedRefinedSchema values", async () => { const rule = createSchemaFieldRule(NestedRefinedSchema); @@ -46,7 +46,7 @@ describe("createSchemaFieldValidator", () => { await expect( rule(formInstance).validator?.(fieldRule, {}, () => {}), - ).rejects.toEqual("Required"); + ).rejects.toEqual(["Required"]); }); it("should reject invalid NestedRefinedSchema values", async () => { const rule = createSchemaFieldRule(NestedRefinedSchema); @@ -55,6 +55,6 @@ describe("createSchemaFieldValidator", () => { await expect( rule(formInstance).validator?.(fieldRule, {}, () => {}), - ).rejects.toEqual("Must be Luka"); + ).rejects.toEqual(["Must be Luka"]); }); }); diff --git a/src/utils/formatErrors.spec.ts b/src/utils/formatErrors.spec.ts index adeb4e4..11feaae 100644 --- a/src/utils/formatErrors.spec.ts +++ b/src/utils/formatErrors.spec.ts @@ -1,7 +1,16 @@ -import z from "zod"; +import z, { ZodError, ZodIssue } from "zod"; import formatErrors from "./formatErrors"; import prepareValues from "./prepareValues"; +const fakeIssues: ZodIssue[] = [ + { code: "custom", message: "Error one", path: ["field"] }, + { code: "custom", message: "Error two", path: ["field"] }, +]; + +const fakeZodError = new ZodError(fakeIssues); + +const schema = z.object({ field: z.string() }); + describe("formatErrors", () => { it("should return empty errors", async () => { const schema = z.object({ @@ -27,7 +36,7 @@ describe("formatErrors", () => { } const formattedErrors = formatErrors<{}>(schema, res.error); - expect(formattedErrors).toEqual({ prop: "Required" }); + expect(formattedErrors).toEqual({ prop: ["Required"] }); }); it("should format nested prop.child error", async () => { const schema = z.object({ @@ -42,6 +51,11 @@ describe("formatErrors", () => { } const formattedErrors = formatErrors<{}>(schema, res.error); - expect(formattedErrors).toEqual({ "prop.child": "Required" }); + expect(formattedErrors).toEqual({ "prop.child": ["Required"] }); + }); + it("should return multiple errors", () => { + const formattedErrors = formatErrors(schema, fakeZodError); + console.log(formattedErrors) + expect(formattedErrors).toEqual({ field: ["Error one", "Error two"] }); }); }); diff --git a/src/utils/formatErrors.ts b/src/utils/formatErrors.ts index 17a5774..d22f5ad 100644 --- a/src/utils/formatErrors.ts +++ b/src/utils/formatErrors.ts @@ -7,7 +7,7 @@ import getIssueAntdPath from "./getIssueAntdPath"; const formatErrors = ( schema: ZodTypeAny, errors: ZodError, -): { [key: string]: string } => { +): { [key: string]: string[] } => { if (errors.issues.length === 0) { return {}; } @@ -22,14 +22,18 @@ const formatErrors = ( (formattedErrors, issue) => { try { const path = getIssueAntdPath(schema, issue); - formattedErrors[path] = issue.message; + if (formattedErrors[path]) { + formattedErrors[path].push(issue.message); + } else { + formattedErrors[path] = [issue.message]; + } } catch (e) { console.warn(e); } return formattedErrors; }, - {} as { [key: string]: string }, + {} as { [key: string]: string[] }, ); }; diff --git a/src/utils/validateFields.spec.ts b/src/utils/validateFields.spec.ts index f072180..47cac8a 100644 --- a/src/utils/validateFields.spec.ts +++ b/src/utils/validateFields.spec.ts @@ -12,36 +12,36 @@ describe("validateFields", () => { }); it("should return errors", async () => { expect(await validateFields(NameSchema, {})).toEqual({ - name: "Required", + name: ["Required"], }); }); it("should return errors for nested schemas", async () => { expect(await validateFields(NestedRefinedSchema, {})).toEqual({ - "user.name": "Required", + "user.name": ["Required"], }); }); it("should return errors for primitive array field", async () => { expect(await validateFields(ArrayNumberFieldSchema, {})).toEqual({ - numbers: "Required", + numbers: ["Required"], }); }); it("should return errors for object array field", async () => { expect( await validateFields(ArrayUserFieldSchema, { users: "invalid value" }), ).toEqual({ - users: "Expected array, received string", + users: ["Expected array, received string"], }); }); it("should return errors for object array field items", async () => { expect(await validateFields(ArrayUserFieldSchema, { users: [1] })).toEqual({ - "users.0": "Expected object, received number", + "users.0": ["Expected object, received number"], }); expect( await validateFields(ArrayUserFieldSchema, { users: [{ name: 10 }] }), ).toEqual({ - "users.0.name": "Expected string, received number", + "users.0.name": ["Expected string, received number"], }); }); }); diff --git a/src/utils/validateFields.ts b/src/utils/validateFields.ts index d2290ce..a9a32c5 100644 --- a/src/utils/validateFields.ts +++ b/src/utils/validateFields.ts @@ -6,13 +6,13 @@ import formatErrors from "./formatErrors"; const validateFields = async ( schema: AntdFormZodSchema, values: {}, -): Promise<{ [key: string]: string }> => { +): Promise<{ [key: string]: string[] }> => { const valuesWithPlaceholders = prepareValues(schema, values); const res = await schema.safeParseAsync(valuesWithPlaceholders); if (res.success) { - return {} as Record; + return {} as Record; } return formatErrors(schema, res.error); diff --git a/stories/basic.stories.tsx b/stories/basic.stories.tsx index a7222bb..24bfd04 100644 --- a/stories/basic.stories.tsx +++ b/stories/basic.stories.tsx @@ -30,6 +30,7 @@ const Child = z.object({ }); const BasicSchema = z.object({ + email: z.string().email().min(5).max(15).includes('.com'), name: z.string().refine((value) => value.length > 2, { message: "Must have more than 2 chars", }), @@ -46,6 +47,9 @@ const rule = createSchemaFieldRule(BasicSchema); const BasicForm = () => { return (
+ + +