diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index ae11c541d..9d26d18bc 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -10,6 +10,7 @@ import { enum as enum_, function as function_, instanceof as instanceof_, + infer as infer_, intersection, lazy, literal, @@ -43,7 +44,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(issues?.[0]); @@ -52,7 +54,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(null); @@ -65,7 +68,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(null); @@ -84,11 +88,36 @@ describe('ZodBridge', () => { path: ['b'], }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: 'a', b: 'b' }); expect(error?.issues?.[0]?.message).toBe(errorMessage); }); + it('works with chained refined schema', () => { + const firstErrorMessage = 'Different values'; + const secondErrorMessage = 'Different results'; + + 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'], + }); + + type SchemaType = infer_; + 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'; @@ -104,10 +133,43 @@ describe('ZodBridge', () => { } }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); 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 results'; + + 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, + }); + } + }); + + type SchemaType = infer_; + 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', () => { @@ -120,7 +182,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(issues?.[0].message); @@ -129,7 +192,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(''); @@ -142,7 +206,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(''); @@ -167,7 +232,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const messages = ['A: Required', 'B: Required']; expect(bridge.getErrorMessages(error)).toEqual(messages); @@ -175,7 +241,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const messages = [ 'A (0, 2): Expected string, received number', @@ -186,7 +253,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const messages = ['C: Expected string, received number']; expect(bridge.getErrorMessages(error)).toEqual(messages); @@ -244,6 +312,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', () => { @@ -686,7 +765,8 @@ describe('ZodBridge', () => { describe('#getValidator', () => { it('return a function', () => { const schema = object({}); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const validator = bridge.getValidator(); expect(validator).toEqual(expect.any(Function)); expect(validator({})).toEqual(null); diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index ec3da0af0..ef6d14592 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -17,7 +17,6 @@ import { ZodNumberDef, ZodObject, ZodOptional, - ZodRawShape, ZodString, ZodType, } from 'zod'; @@ -55,15 +54,15 @@ type Option = { value: Value; }; -export default class ZodBridge extends Bridge { - schema: ZodObject | ZodEffects>; +export default class ZodBridge extends Bridge { + schema: ZodType; provideDefaultLabelFromFieldName: boolean; constructor({ schema, provideDefaultLabelFromFieldName = true, }: { - schema: ZodObject | ZodEffects>; + schema: ZodType; provideDefaultLabelFromFieldName?: boolean; }) { super(); @@ -108,8 +107,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; + while (field instanceof ZodEffects) { + field = field.innerType(); } for (const key of joinName(null, name)) {