diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index ae11c541d..4a39581d7 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -89,6 +89,29 @@ describe('ZodBridge', () => { expect(error?.issues?.[0]?.message).toBe(errorMessage); }); + it('works with chained refined schema', () => { + const firstErrorMessage = 'Different values'; + const secondErrorMessage = 'Different moduloes of 2 and 3'; + + const schema = object({ + a: number(), + b: number(), + }) + .refine(({ a, b }) => a === b, { + message: firstErrorMessage, + path: ['b'], + }) + .refine(({ a, b }) => a % 2 === b % 3, { + message: secondErrorMessage, + path: ['b'], + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 1, b: 2 }); + expect(error?.issues?.[0]?.message).toBe(firstErrorMessage); + expect(error?.issues?.[1]?.message).toBe(secondErrorMessage); + }); + it('works with super refined schema', () => { const errorMessage = 'Different values'; @@ -108,6 +131,37 @@ describe('ZodBridge', () => { const error = bridge.getValidator()({ a: 'a', b: 'b' }); expect(error?.issues?.[0]?.message).toBe(errorMessage); }); + + it('works with chained super refined schema', () => { + const firstErrorMessage = 'Different values'; + const secondErrorMessage = 'Different moduloes of 2 and 3'; + + const schema = object({ + a: number(), + b: number(), + }) + .superRefine((val, ctx) => { + if (val.a !== val.b) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: firstErrorMessage, + }); + } + }) + .superRefine((val, ctx) => { + if (val.a % 2 !== val.b % 3) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: secondErrorMessage, + }); + } + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 1, b: 2 }); + expect(error?.issues?.[0]?.message).toBe(firstErrorMessage); + expect(error?.issues?.[1]?.message).toBe(secondErrorMessage); + }); }); describe('#getErrorMessage', () => { @@ -244,6 +298,17 @@ describe('ZodBridge', () => { const bridge = new ZodBridge({ schema }); expect(bridge.getField('')).toBe(schema._def.schema); }); + + it('works with nested ZodEffects', () => { + const schema = object({}) + .refine(data => data) + .refine(data => data) + .refine(data => data); + const bridge = new ZodBridge({ schema }); + expect(bridge.getField('')).toBe( + schema._def.schema._def.schema._def.schema, + ); + }); }); describe('#getInitialValue', () => { diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index ec3da0af0..890b95dce 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -55,15 +55,27 @@ type Option = { value: Value; }; +type BuildTuple = T['length'] extends L + ? T + : BuildTuple; + +type Decrement = + BuildTuple extends [any, ...infer R] ? R['length'] : never; + +type NestedZodEffect< + T extends ZodObject, + Depth extends number = 20, +> = Depth extends 0 ? T : ZodEffects>>; + export default class ZodBridge extends Bridge { - schema: ZodObject | ZodEffects>; + schema: ZodObject | NestedZodEffect>; provideDefaultLabelFromFieldName: boolean; constructor({ schema, provideDefaultLabelFromFieldName = true, }: { - schema: ZodObject | ZodEffects>; + schema: ZodObject | NestedZodEffect>; provideDefaultLabelFromFieldName?: boolean; }) { super(); @@ -106,11 +118,8 @@ export default class ZodBridge extends Bridge { } getField(name: string) { - let field: ZodType = this.schema; - - if (this.schema instanceof ZodEffects) { - field = this.schema._def.schema; - } + let field: ZodType = + this.schema instanceof ZodEffects ? this.schema.innerType() : this.schema; for (const key of joinName(null, name)) { if (field instanceof ZodDefault) {