Skip to content

Commit

Permalink
feat: add Vest sync validation + update to v3 (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre authored Jan 23, 2021
1 parent f83996b commit e76743d
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 205 deletions.
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,18 @@
},
"homepage": "https://react-hook-form.com",
"devDependencies": {
"@testing-library/jest-dom": "^5.11.8",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.6.0",
"@types/jest": "^26.0.19",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.3",
"@testing-library/user-event": "^12.6.2",
"@types/jest": "^26.0.20",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.11.1",
"@typescript-eslint/parser": "^4.11.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"check-export-map": "^1.0.1",
"eslint": "^7.17.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-prettier": "^3.3.0",
"husky": "^4.3.6",
"eslint": "^7.18.0",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-prettier": "^3.3.1",
"husky": "^4.3.8",
"jest": "^26.6.3",
"joi": "^17.3.0",
"lint-staged": "^10.5.3",
Expand All @@ -129,12 +129,12 @@
"prettier": "^2.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-hook-form": "^6.14.0",
"semantic-release": "^17.3.1",
"superstruct": "^0.13.1",
"react-hook-form": "^6.14.2",
"semantic-release": "^17.3.6",
"superstruct": "^0.13.3",
"ts-jest": "^26.4.4",
"typescript": "^4.1.3",
"vest": "^2.2.3",
"vest": "^3.1.1",
"yup": "^0.32.8",
"zod": "^1.11.11"
},
Expand Down
113 changes: 113 additions & 0 deletions vest/src/__tests__/__snapshots__/vest.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`vestResolver should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true 1`] = `
Object {
"errors": Object {
"deepObject": Object {
"data": Object {
"message": "deepObject.data is required",
"type": "",
"types": Object {
"0": "deepObject.data is required",
},
},
},
"password": Object {
"message": "Password must be at least 5 chars",
"type": "",
"types": Object {
"0": "Password must be at least 5 chars",
"1": "Password must contain a digit",
"2": "Password must contain a symbol",
},
},
"username": Object {
"message": "Username is required",
"type": "",
"types": Object {
"0": "Username is required",
"1": "Must be longer than 3 chars",
},
},
},
"values": Object {},
}
`;

exports[`vestResolver should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true and \`mode: sync\` 1`] = `
Object {
"errors": Object {
"deepObject": Object {
"data": Object {
"message": "deepObject.data is required",
"type": "",
"types": Object {
"0": "deepObject.data is required",
},
},
},
"password": Object {
"message": "Password must be at least 5 chars",
"type": "",
"types": Object {
"0": "Password must be at least 5 chars",
"1": "Password must contain a digit",
"2": "Password must contain a symbol",
},
},
"username": Object {
"message": "Username is required",
"type": "",
"types": Object {
"0": "Username is required",
"1": "Must be longer than 3 chars",
},
},
},
"values": Object {},
}
`;

exports[`vestResolver should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false 1`] = `
Object {
"errors": Object {
"deepObject": Object {
"data": Object {
"message": "deepObject.data is required",
"type": "",
},
},
"password": Object {
"message": "Password must be at least 5 chars",
"type": "",
},
"username": Object {
"message": "Username is required",
"type": "",
},
},
"values": Object {},
}
`;

exports[`vestResolver should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false and \`mode: sync\` 1`] = `
Object {
"errors": Object {
"deepObject": Object {
"data": Object {
"message": "deepObject.data is required",
"type": "",
},
},
"password": Object {
"message": "Password must be at least 5 chars",
"type": "",
},
"username": Object {
"message": "Username is required",
"type": "",
},
},
"values": Object {},
}
`;
100 changes: 51 additions & 49 deletions vest/src/__tests__/vest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,23 @@ describe('vestResolver', () => {
data: 'test',
},
};
expect(await vestResolver(validationSuite)(data, {})).toEqual({
expect(await vestResolver(validationSuite)(data)).toEqual({
values: data,
errors: {},
});
});

it('should return values from vestResolver with `mode: sync` when validation pass', async () => {
const data = {
username: 'asdda',
password: 'asddfg123!',
deepObject: {
data: 'test',
},
};
expect(
await vestResolver(validationSuite, undefined, { mode: 'sync' })(data),
).toEqual({
values: data,
errors: {},
});
Expand All @@ -55,25 +71,21 @@ describe('vestResolver', () => {
},
};

expect(await vestResolver(validationSuite)(data, {})).toEqual({
values: {},
errors: {
username: {
type: '',
message: 'Username is required',
},
password: {
type: '',
message: 'Password must be at least 5 chars',
},
deepObject: {
data: {
type: '',
message: 'deepObject.data is required',
},
},
expect(await vestResolver(validationSuite)(data)).toMatchSnapshot();
});

it('should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false and `mode: sync`', async () => {
const data = {
username: '',
password: 'a',
deepObject: {
data: '',
},
});
};

expect(
await vestResolver(validationSuite, undefined, { mode: 'sync' })(data),
).toMatchSnapshot();
});

it('should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true', async () => {
Expand All @@ -85,36 +97,26 @@ describe('vestResolver', () => {
},
};

expect(await vestResolver(validationSuite, {})(data, {}, true)).toEqual({
values: {},
errors: {
username: {
type: '',
message: 'Username is required',
types: {
0: 'Username is required',
1: 'Must be longer than 3 chars',
},
},
password: {
type: '',
message: 'Password must be at least 5 chars',
types: {
0: 'Password must be at least 5 chars',
1: 'Password must contain a digit',
2: 'Password must contain a symbol',
},
},
deepObject: {
data: {
type: '',
message: 'deepObject.data is required',
types: {
0: 'deepObject.data is required',
},
},
},
expect(
await vestResolver(validationSuite)(data, {}, true),
).toMatchSnapshot();
});

it('should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true and `mode: sync`', async () => {
const data = {
username: '',
password: 'a',
deepObject: {
data: '',
},
});
};

expect(
await vestResolver(validationSuite, undefined, { mode: 'sync' })(
data,
{},
true,
),
).toMatchSnapshot();
});
});
7 changes: 2 additions & 5 deletions vest/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ export type ICreateResult = ReturnType<typeof Vest.create>;

export type Resolver = (
schema: ICreateResult,
options?: any,
schemaOptions?: never,
resolverOptions?: { mode: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
context?: TContext,
validateAllFieldCriteria?: boolean,
) => Promise<ResolverResult<TFieldValues>>;

export type VestErrors = Record<string, string[]>;

export type Promisify = <T extends ICreateResult, K>(
fn: T,
) => (args: K) => Promise<Vest.IVestResult>;
30 changes: 17 additions & 13 deletions vest/src/vest.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { transformToNestObject } from 'react-hook-form';
import * as Vest from 'vest';
import type { Promisify, VestErrors, Resolver } from './types';

const promisify: Promisify = (validatorFn) => (...args) =>
new Promise((resolve) => validatorFn(...args).done(resolve as Vest.DoneCB));
import promisify from 'vest/promisify';
import { DraftResult, IVestResult } from 'vest/vestResult';
import type { VestErrors, Resolver } from './types';

const parseErrorSchema = (
vestError: VestErrors,
Expand All @@ -30,17 +28,23 @@ const parseErrorSchema = (
}, {});
};

export const vestResolver: Resolver = (schema, _ = {}) => async (
values,
_context,
validateAllFieldCriteria = false,
) => {
const validateSchema = promisify(schema);
const result = await validateSchema(values);
export const vestResolver: Resolver = (
schema,
_,
{ mode } = { mode: 'async' },
) => async (values, _context, validateAllFieldCriteria = false) => {
let result: IVestResult | DraftResult;
if (mode === 'async') {
const validateSchema = promisify(schema);
result = await validateSchema(values);
} else {
result = schema(values);
}

const errors = result.getErrors();

if (!result.hasErrors()) {
return { values: values as any, errors: {} };
return { values, errors: {} };
}

return {
Expand Down
Loading

0 comments on commit e76743d

Please sign in to comment.