v1.0 with SW PWA enabled
This commit is contained in:
89
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
89
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as vest from 'vest';
|
||||
import { vestResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_SYMBOL_MESSAGE = 'password must contain a symbol';
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const validationSuite = vest.create('form', (data: FormData) => {
|
||||
vest.test('username', USERNAME_REQUIRED_MESSAGE, () => {
|
||||
vest.enforce(data.username).isNotEmpty();
|
||||
});
|
||||
|
||||
vest.test('password', PASSWORD_SYMBOL_MESSAGE, () => {
|
||||
vest.enforce(data.password).isNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: vestResolver(validationSuite),
|
||||
shouldUseNativeValidation: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input {...register('username')} placeholder="username" />
|
||||
|
||||
<input {...register('password')} placeholder="password" />
|
||||
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
test("form's native validation with Vest", async () => {
|
||||
const handleSubmit = vi.fn();
|
||||
render(<TestComponent onSubmit={handleSubmit} />);
|
||||
|
||||
// username
|
||||
let usernameField = screen.getByPlaceholderText(
|
||||
/username/i,
|
||||
) as HTMLInputElement;
|
||||
expect(usernameField.validity.valid).toBe(true);
|
||||
expect(usernameField.validationMessage).toBe('');
|
||||
|
||||
// password
|
||||
let passwordField = screen.getByPlaceholderText(
|
||||
/password/i,
|
||||
) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(true);
|
||||
expect(passwordField.validationMessage).toBe('');
|
||||
|
||||
await user.click(screen.getByText(/submit/i));
|
||||
|
||||
// username
|
||||
usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement;
|
||||
expect(usernameField.validity.valid).toBe(false);
|
||||
expect(usernameField.validationMessage).toBe(USERNAME_REQUIRED_MESSAGE);
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe(PASSWORD_SYMBOL_MESSAGE);
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/username/i), 'joe');
|
||||
await user.type(screen.getByPlaceholderText(/password/i), 'password');
|
||||
|
||||
// username
|
||||
usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement;
|
||||
expect(usernameField.validity.valid).toBe(true);
|
||||
expect(usernameField.validationMessage).toBe('');
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(true);
|
||||
expect(passwordField.validationMessage).toBe('');
|
||||
});
|
||||
60
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/Form.tsx
generated
vendored
Normal file
60
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/Form.tsx
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as vest from 'vest';
|
||||
import { vestResolver } from '..';
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const validationSuite = vest.create('form', (data: FormData) => {
|
||||
vest.test('username', 'Username is required', () => {
|
||||
vest.enforce(data.username).isNotEmpty();
|
||||
});
|
||||
|
||||
vest.test('password', 'Password must contain a symbol', () => {
|
||||
vest.enforce(data.password).matches(/[^A-Za-z0-9]/);
|
||||
});
|
||||
});
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: vestResolver(validationSuite), // Useful to check TypeScript regressions
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input {...register('username')} />
|
||||
{errors.username && <span role="alert">{errors.username.message}</span>}
|
||||
|
||||
<input {...register('password')} />
|
||||
{errors.password && <span role="alert">{errors.password.message}</span>}
|
||||
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
test("form's validation with Vest and TypeScript's integration", async () => {
|
||||
const handleSubmit = vi.fn();
|
||||
render(<TestComponent onSubmit={handleSubmit} />);
|
||||
|
||||
expect(screen.queryAllByRole('alert')).toHaveLength(0);
|
||||
|
||||
await user.click(screen.getByText(/submit/i));
|
||||
|
||||
expect(screen.getByText(/Username is required/i)).toBeVisible();
|
||||
expect(screen.getByText(/Password must contain a symbol/i)).toBeVisible();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
67
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
67
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
import * as vest from 'vest';
|
||||
|
||||
export const validationSuite = vest.create('form', (data: any = {}) => {
|
||||
vest.test('username', 'Username is required', () => {
|
||||
vest.enforce(data.username).isNotEmpty();
|
||||
});
|
||||
|
||||
vest.test('username', 'Must be longer than 3 chars', () => {
|
||||
vest.enforce(data.username).longerThan(3);
|
||||
});
|
||||
|
||||
vest.test('deepObject.data', 'deepObject.data is required', () => {
|
||||
vest.enforce(data.deepObject.data).isNotEmpty();
|
||||
});
|
||||
|
||||
vest.test('password', 'Password is required', () => {
|
||||
vest.enforce(data.password).isNotEmpty();
|
||||
});
|
||||
|
||||
vest.test('password', 'Password must be at least 5 chars', () => {
|
||||
vest.enforce(data.password).longerThanOrEquals(5);
|
||||
});
|
||||
|
||||
vest.test('password', 'Password must contain a digit', () => {
|
||||
vest.enforce(data.password).matches(/[0-9]/);
|
||||
});
|
||||
|
||||
vest.test('password', 'Password must contain a symbol', () => {
|
||||
vest.enforce(data.password).matches(/[^A-Za-z0-9]/);
|
||||
});
|
||||
});
|
||||
|
||||
export const validData = {
|
||||
username: 'asdda',
|
||||
password: 'asddfg123!',
|
||||
deepObject: {
|
||||
data: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
username: '',
|
||||
password: 'a',
|
||||
deepObject: {
|
||||
data: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const fields: Record<InternalFieldName, Field['_f']> = {
|
||||
username: {
|
||||
ref: { name: 'username' },
|
||||
name: 'username',
|
||||
},
|
||||
password: {
|
||||
ref: { name: 'password' },
|
||||
name: 'password',
|
||||
},
|
||||
email: {
|
||||
ref: { name: 'email' },
|
||||
name: 'email',
|
||||
},
|
||||
birthday: {
|
||||
ref: { name: 'birthday' },
|
||||
name: 'birthday',
|
||||
},
|
||||
};
|
||||
135
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/__snapshots__/vest.ts.snap
generated
vendored
Normal file
135
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/__snapshots__/vest.ts.snap
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`vestResolver > should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "deepObject.data is required",
|
||||
"ref": undefined,
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "deepObject.data is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "Password must be at least 5 chars",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "Password must be at least 5 chars",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "Username is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "Username is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`vestResolver > should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "deepObject.data is required",
|
||||
"ref": undefined,
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "deepObject.data is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "Password must be at least 5 chars",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "Password must be at least 5 chars",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "Username is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "",
|
||||
"types": {
|
||||
"0": "Username is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`vestResolver > should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "deepObject.data is required",
|
||||
"ref": undefined,
|
||||
"type": "",
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "Password must be at least 5 chars",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "",
|
||||
},
|
||||
"username": {
|
||||
"message": "Username is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`vestResolver > should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "deepObject.data is required",
|
||||
"ref": undefined,
|
||||
"type": "",
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "Password must be at least 5 chars",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "",
|
||||
},
|
||||
"username": {
|
||||
"message": "Username is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
90
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/vest.ts
generated
vendored
Normal file
90
frontend/node_modules/@hookform/resolvers/vest/src/__tests__/vest.ts
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
import { vestResolver } from '..';
|
||||
import {
|
||||
fields,
|
||||
invalidData,
|
||||
validData,
|
||||
validationSuite,
|
||||
} from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('vestResolver', () => {
|
||||
it('should return values from vestResolver when validation pass', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toEqual({
|
||||
values: validData,
|
||||
errors: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return values from vestResolver with `mode: sync` when validation pass', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite, undefined, {
|
||||
mode: 'sync',
|
||||
})(validData, undefined, { fields, shouldUseNativeValidation }),
|
||||
).toEqual({
|
||||
values: validData,
|
||||
errors: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return single error message from vestResolver when validation fails and validateAllFieldCriteria set to false and `mode: sync`', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite, undefined, {
|
||||
mode: 'sync',
|
||||
})(invalidData, undefined, { fields, shouldUseNativeValidation }),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite)(
|
||||
invalidData,
|
||||
{},
|
||||
{ fields, criteriaMode: 'all', shouldUseNativeValidation },
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from vestResolver when validation fails and validateAllFieldCriteria set to true and `mode: sync`', async () => {
|
||||
expect(
|
||||
await vestResolver(validationSuite, undefined, { mode: 'sync' })(
|
||||
invalidData,
|
||||
{},
|
||||
{ fields, criteriaMode: 'all', shouldUseNativeValidation },
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should call a suite with values, validated field names and a context as arguments', async () => {
|
||||
const suite = vi.fn(validationSuite) as any as typeof validationSuite;
|
||||
|
||||
await vestResolver(suite)(
|
||||
validData,
|
||||
{ some: 'context' },
|
||||
{
|
||||
fields: { username: fields.username },
|
||||
names: ['username'],
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(suite).toHaveBeenCalledTimes(1);
|
||||
expect(suite).toHaveBeenCalledWith(validData, ['username'], {
|
||||
some: 'context',
|
||||
});
|
||||
});
|
||||
});
|
||||
2
frontend/node_modules/@hookform/resolvers/vest/src/index.ts
generated
vendored
Normal file
2
frontend/node_modules/@hookform/resolvers/vest/src/index.ts
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './vest';
|
||||
export * from './types';
|
||||
30
frontend/node_modules/@hookform/resolvers/vest/src/types.ts
generated
vendored
Normal file
30
frontend/node_modules/@hookform/resolvers/vest/src/types.ts
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
FieldName,
|
||||
FieldValues,
|
||||
ResolverOptions,
|
||||
ResolverResult,
|
||||
} from 'react-hook-form';
|
||||
import * as Vest from 'vest';
|
||||
|
||||
export type ICreateResult<
|
||||
TValues extends FieldValues = FieldValues,
|
||||
TContext = any,
|
||||
> = ReturnType<
|
||||
typeof Vest.create<
|
||||
any,
|
||||
any,
|
||||
(values: TValues, names?: FieldName<TValues>[], context?: TContext) => void
|
||||
>
|
||||
>;
|
||||
|
||||
export type Resolver = <TValues extends FieldValues, TContext>(
|
||||
schema: ICreateResult<TValues, TContext>,
|
||||
schemaOptions?: never,
|
||||
factoryOptions?: { mode?: 'async' | 'sync'; rawValues?: boolean },
|
||||
) => (
|
||||
values: TValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TValues>,
|
||||
) => Promise<ResolverResult<TValues>>;
|
||||
|
||||
export type VestErrors = Record<string, string[]>;
|
||||
51
frontend/node_modules/@hookform/resolvers/vest/src/vest.ts
generated
vendored
Normal file
51
frontend/node_modules/@hookform/resolvers/vest/src/vest.ts
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import { FieldError } from 'react-hook-form';
|
||||
import promisify from 'vest/promisify';
|
||||
import type { Resolver, VestErrors } from './types';
|
||||
|
||||
const parseErrorSchema = (
|
||||
vestError: VestErrors,
|
||||
validateAllFieldCriteria: boolean,
|
||||
) => {
|
||||
const errors: Record<string, FieldError> = {};
|
||||
for (const path in vestError) {
|
||||
if (!errors[path]) {
|
||||
errors[path] = { message: vestError[path][0], type: '' };
|
||||
}
|
||||
|
||||
if (validateAllFieldCriteria) {
|
||||
errors[path].types = vestError[path].reduce<Record<number, string>>(
|
||||
(acc, message, index) => (acc[index] = message) && acc,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
export const vestResolver: Resolver =
|
||||
(schema, _, resolverOptions = {}) =>
|
||||
async (values, context, options) => {
|
||||
const result =
|
||||
resolverOptions.mode === 'sync'
|
||||
? schema(values, options.names, context)
|
||||
: await promisify(schema)(values, options.names, context);
|
||||
|
||||
if (result.hasErrors()) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrorSchema(
|
||||
result.getErrors(),
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return { values, errors: {} };
|
||||
};
|
||||
Reference in New Issue
Block a user