Skip to content

Commit

Permalink
feat: add Yup sync validation (#115)
Browse files Browse the repository at this point in the history
* feat: add Yup sync validation

* test(zod): add tests

* test(zod): update tests

* test(yup): add validateAllCriteria test
  • Loading branch information
jorisre authored Jan 22, 2021
1 parent 8e3b54a commit f83996b
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 8 deletions.
49 changes: 49 additions & 0 deletions yup/src/__tests__/__snapshots__/yup.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
66 changes: 63 additions & 3 deletions yup/src/__tests__/yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof schema> = {
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 });
});

Expand All @@ -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: '___',
Expand All @@ -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 };
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion yup/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ type Options<T extends Yup.AnyObjectSchema> = Parameters<T['validate']>[1];

export type Resolver = <T extends Yup.AnyObjectSchema>(
schema: T,
options?: Options<T>,
schemaOptions?: Options<T>,
resolverOptions?: { mode: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
context?: TContext,
Expand Down
17 changes: 13 additions & 4 deletions yup/src/yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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) {
Expand Down
64 changes: 64 additions & 0 deletions zod/src/__tests__/__snapshots__/zod.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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 {},
}
`;
29 changes: 29 additions & 0 deletions zod/src/__tests__/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});

Expand All @@ -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 });
});

Expand All @@ -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();
});

Expand All @@ -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();
});
});

0 comments on commit f83996b

Please sign in to comment.