diff --git a/yup/src/__tests__/__snapshots__/yup.ts.snap b/yup/src/__tests__/__snapshots__/yup.ts.snap index 1b8e0b73..9fd474b2 100644 --- a/yup/src/__tests__/__snapshots__/yup.ts.snap +++ b/yup/src/__tests__/__snapshots__/yup.ts.snap @@ -20,6 +20,26 @@ Object { } `; +exports[`yupResolver should return a single error from yupResolver with \`mode: sync\` when validation fails 1`] = ` +Object { + "errors": Object { + "birthYear": Object { + "message": "birthYear must be a \`number\` type, but the final value was: \`NaN\` (cast from the value \`\\"birthYear\\"\`).", + "type": "typeError", + }, + "password": Object { + "message": "password must match the following: \\"/^[a-zA-Z0-9]{3,30}/\\"", + "type": "matches", + }, + "username": Object { + "message": "username is a required field", + "type": "required", + }, + }, + "values": Object {}, +} +`; + exports[`yupResolver should return all the errors from yupResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = ` Object { "errors": Object { @@ -49,6 +69,35 @@ Object { } `; +exports[`yupResolver should return all the errors from yupResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = ` +Object { + "errors": Object { + "birthYear": Object { + "message": "birthYear must be a \`number\` type, but the final value was: \`NaN\` (cast from the value \`\\"birthYear\\"\`).", + "type": "typeError", + "types": Object { + "typeError": "birthYear must be a \`number\` type, but the final value was: \`NaN\` (cast from the value \`\\"birthYear\\"\`).", + }, + }, + "password": Object { + "message": "password must match the following: \\"/^[a-zA-Z0-9]{3,30}/\\"", + "type": "matches", + "types": Object { + "matches": "password must match the following: \\"/^[a-zA-Z0-9]{3,30}/\\"", + }, + }, + "username": Object { + "message": "username is a required field", + "type": "required", + "types": Object { + "required": "username is a required field", + }, + }, + }, + "values": Object {}, +} +`; + exports[`yupResolver should return an error from yupResolver when validation fails and pass down the yup context 1`] = ` Object { "errors": Object { diff --git a/yup/src/__tests__/yup.ts b/yup/src/__tests__/yup.ts index 9e9b61d4..7994d2e3 100644 --- a/yup/src/__tests__/yup.ts +++ b/yup/src/__tests__/yup.ts @@ -29,8 +29,35 @@ describe('yupResolver', () => { accessToken: 'accessToken', }; + const schemaSpy = jest.spyOn(schema, 'validate'); + const schemaSyncSpy = jest.spyOn(schema, 'validateSync'); + const result = await yupResolver(schema)(data); + expect(schemaSpy).toHaveBeenCalledTimes(1); + expect(schemaSyncSpy).not.toHaveBeenCalled(); + expect(result).toEqual({ errors: {}, values: data }); + }); + + it('should return values from yupResolver with `mode: sync` when validation pass', async () => { + const data: yup.InferType = { + username: 'Doe', + password: 'Password123', + repeatPassword: 'Password123', + birthYear: 2000, + email: 'john@doe.com', + tags: ['tag1', 'tag2'], + enabled: true, + accessToken: 'accessToken', + }; + + const validateSyncSpy = jest.spyOn(schema, 'validateSync'); + const validateSpy = jest.spyOn(schema, 'validate'); + + const result = await yupResolver(schema, undefined, { mode: 'sync' })(data); + + expect(validateSyncSpy).toHaveBeenCalledTimes(1); + expect(validateSpy).not.toHaveBeenCalled(); expect(result).toEqual({ errors: {}, values: data }); }); @@ -46,6 +73,23 @@ describe('yupResolver', () => { expect(result).toMatchSnapshot(); }); + it('should return a single error from yupResolver with `mode: sync` when validation fails', async () => { + const data = { + password: '___', + email: '', + birthYear: 'birthYear', + }; + + const validateSyncSpy = jest.spyOn(schema, 'validateSync'); + const validateSpy = jest.spyOn(schema, 'validate'); + + const result = await yupResolver(schema, undefined, { mode: 'sync' })(data); + + expect(validateSyncSpy).toHaveBeenCalledTimes(1); + expect(validateSpy).not.toHaveBeenCalled(); + expect(result).toMatchSnapshot(); + }); + it('should return all the errors from yupResolver when validation fails with `validateAllFieldCriteria` set to true', async () => { const data = { password: '___', @@ -58,6 +102,22 @@ describe('yupResolver', () => { expect(result).toMatchSnapshot(); }); + it('should return all the errors from yupResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => { + const data = { + password: '___', + email: '', + birthYear: 'birthYear', + }; + + const result = await yupResolver(schema, undefined, { mode: 'sync' })( + data, + undefined, + true, + ); + + expect(result).toMatchSnapshot(); + }); + it('should return an error from yupResolver when validation fails and pass down the yup context', async () => { const data = { name: 'eric' }; const context = { min: true }; @@ -70,11 +130,11 @@ describe('yupResolver', () => { }), }); - const schemaSpyValidate = jest.spyOn(schemaWithContext, 'validate'); + const validateSpy = jest.spyOn(schemaWithContext, 'validate'); const result = await yupResolver(schemaWithContext)(data, context); - expect(schemaSpyValidate).toHaveBeenCalledTimes(1); - expect(schemaSpyValidate).toHaveBeenCalledWith( + expect(validateSpy).toHaveBeenCalledTimes(1); + expect(validateSpy).toHaveBeenCalledWith( data, expect.objectContaining({ abortEarly: false, diff --git a/yup/src/types.ts b/yup/src/types.ts index bd7a8ae6..f72b7cc9 100644 --- a/yup/src/types.ts +++ b/yup/src/types.ts @@ -9,7 +9,8 @@ type Options = Parameters[1]; export type Resolver = ( schema: T, - options?: Options, + schemaOptions?: Options, + resolverOptions?: { mode: 'async' | 'sync' }, ) => ( values: UnpackNestedValue, context?: TContext, diff --git a/yup/src/yup.ts b/yup/src/yup.ts index 72169711..ccc5668e 100644 --- a/yup/src/yup.ts +++ b/yup/src/yup.ts @@ -59,6 +59,7 @@ export const yupResolver: Resolver = ( options = { abortEarly: false, }, + { mode } = { mode: 'async' }, ) => async (values, context, validateAllFieldCriteria = false) => { try { if (options.context && process.env.NODE_ENV === 'development') { @@ -68,11 +69,19 @@ export const yupResolver: Resolver = ( ); } + const result = + mode === 'async' + ? await schema.validate(values, { + ...options, + context, + }) + : schema.validateSync(values, { + ...options, + context, + }); + return { - values: await schema.validate(values, { - ...options, - context, - }), + values: result, errors: {}, }; } catch (e) { diff --git a/zod/src/__tests__/__snapshots__/zod.ts.snap b/zod/src/__tests__/__snapshots__/zod.ts.snap index ab468fff..c3df61a5 100644 --- a/zod/src/__tests__/__snapshots__/zod.ts.snap +++ b/zod/src/__tests__/__snapshots__/zod.ts.snap @@ -143,3 +143,67 @@ Object { "values": Object {}, } `; + +exports[`zodResolver should return all the errors from zodResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = ` +Object { + "errors": Object { + "birthYear": Object { + "message": "Invalid input", + "type": "invalid_union", + "types": Object { + "invalid_union": "Invalid input", + }, + }, + "confirm": Object { + "message": "Passwords don't match", + "type": "custom_error", + "types": Object { + "custom_error": "Passwords don't match", + }, + }, + "email": Object { + "message": "Invalid email", + "type": "invalid_string", + "types": Object { + "invalid_string": "Invalid email", + }, + }, + "enabled": Object { + "message": "Required", + "type": "invalid_type", + "types": Object { + "invalid_type": "Required", + }, + }, + "password": Object { + "message": "Invalid", + "type": "invalid_string", + "types": Object { + "invalid_string": "Invalid", + }, + }, + "repeatPassword": Object { + "message": "Required", + "type": "invalid_type", + "types": Object { + "invalid_type": "Required", + }, + }, + "tags": Object { + "message": "Required", + "type": "invalid_type", + "types": Object { + "invalid_type": "Required", + }, + }, + "username": Object { + "message": "Required", + "type": "invalid_type", + "types": Object { + "invalid_type": "Required", + }, + }, + }, + "values": Object {}, +} +`; diff --git a/zod/src/__tests__/zod.ts b/zod/src/__tests__/zod.ts index 50d9704b..eaa5de69 100644 --- a/zod/src/__tests__/zod.ts +++ b/zod/src/__tests__/zod.ts @@ -29,8 +29,11 @@ describe('zodResolver', () => { enabled: true, }; + const parseAsyncSpy = jest.spyOn(schema, 'parseAsync'); + const result = await zodResolver(schema)(data); + expect(parseAsyncSpy).toHaveBeenCalledTimes(1); expect(result).toEqual({ errors: {}, values: data }); }); @@ -45,8 +48,13 @@ describe('zodResolver', () => { enabled: true, }; + const parseSpy = jest.spyOn(schema, 'parse'); + const parseAsyncSpy = jest.spyOn(schema, 'parseAsync'); + const result = await zodResolver(schema, undefined, { mode: 'sync' })(data); + expect(parseSpy).toHaveBeenCalledTimes(1); + expect(parseAsyncSpy).not.toHaveBeenCalled(); expect(result).toEqual({ errors: {}, values: data }); }); @@ -69,8 +77,13 @@ describe('zodResolver', () => { birthYear: 'birthYear', }; + const parseSpy = jest.spyOn(schema, 'parse'); + const parseAsyncSpy = jest.spyOn(schema, 'parseAsync'); + const result = await zodResolver(schema, undefined, { mode: 'sync' })(data); + expect(parseSpy).toHaveBeenCalledTimes(1); + expect(parseAsyncSpy).not.toHaveBeenCalled(); expect(result).toMatchSnapshot(); }); @@ -85,4 +98,20 @@ describe('zodResolver', () => { expect(result).toMatchSnapshot(); }); + + it('should return all the errors from zodResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => { + const data = { + password: '___', + email: '', + birthYear: 'birthYear', + }; + + const result = await zodResolver(schema, undefined, { mode: 'sync' })( + data, + undefined, + true, + ); + + expect(result).toMatchSnapshot(); + }); });