Skip to content

Commit

Permalink
fix(native-validation): clear error
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre committed Jul 11, 2021
1 parent d8aff3d commit 7bfbb40
Show file tree
Hide file tree
Showing 24 changed files with 305 additions and 131 deletions.
15 changes: 15 additions & 0 deletions class-validator/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,19 @@ test("form's native validation with Class Validator", async () => {
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
expect(passwordField.validity.valid).toBe(false);
expect(passwordField.validationMessage).toBe('password should not be empty');

await act(async () => {
user.type(screen.getByPlaceholderText(/username/i), 'joe');
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('');
});
30 changes: 17 additions & 13 deletions class-validator/src/class-validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldErrors } from 'react-hook-form';
import { toNestError } from '@hookform/resolvers';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import { plainToClass } from 'class-transformer';
import { validate, validateSync, ValidationError } from 'class-validator';
import type { Resolver } from './types';
Expand Down Expand Up @@ -42,17 +42,21 @@ export const classValidatorResolver: Resolver =
? validateSync
: validate)(user, schemaOptions);

return rawErrors.length
? {
values: {},
errors: toNestError(
parseErrors(
rawErrors,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
options,
if (rawErrors.length) {
return {
values: {},
errors: toNestError(
parseErrors(
rawErrors,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
}
: { values, errors: {} };
options,
),
};
}

options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return { values, errors: {} };
};
15 changes: 15 additions & 0 deletions computed-types/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,19 @@ test("form's native validation with computed-types", async () => {
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
expect(passwordField.validity.valid).toBe(false);
expect(passwordField.validationMessage).toBe(PASSWORD_REQUIRED_MESSAGE);

await act(async () => {
user.type(screen.getByPlaceholderText(/username/i), 'joe');
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('');
});
8 changes: 6 additions & 2 deletions computed-types/src/computed-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FieldErrors } from 'react-hook-form';
import { toNestError } from '@hookform/resolvers';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import type { ValidationError } from 'computed-types';
import type { Resolver } from './types';

Expand All @@ -26,9 +26,13 @@ const parseErrorSchema = (
export const computedTypesResolver: Resolver =
(schema) => async (values, _, options) => {
try {
const data = await schema(values);

options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return {
errors: {},
values: await schema(values),
values: data,
};
} catch (error) {
return {
Expand Down
15 changes: 15 additions & 0 deletions io-ts/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,19 @@ test("form's native validation with io-ts", async () => {
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
expect(passwordField.validity.valid).toBe(false);
expect(passwordField.validationMessage).toBe(PASSWORD_REQUIRED_MESSAGE);

await act(async () => {
user.type(screen.getByPlaceholderText(/username/i), 'joe');
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('');
});
15 changes: 10 additions & 5 deletions io-ts/src/io-ts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Either from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/function';
import { toNestError } from '@hookform/resolvers';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import errorsToRecord from './errorsToRecord';
import { Resolver } from './types';

Expand All @@ -19,9 +19,14 @@ export const ioTsResolver: Resolver = (codec) => (values, _context, options) =>
values: {},
errors,
}),
(values) => ({
values,
errors: {},
}),
(values) => {
options.shouldUseNativeValidation &&
validateFieldsNatively({}, options);

return {
values,
errors: {},
};
},
),
);
15 changes: 15 additions & 0 deletions joi/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,19 @@ test("form's native validation with Joi", async () => {
expect(passwordField.validationMessage).toBe(
'"password" is not allowed to be empty',
);

await act(async () => {
user.type(screen.getByPlaceholderText(/username/i), 'joe');
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('');
});
31 changes: 19 additions & 12 deletions joi/src/joi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { appendErrors, FieldError } from 'react-hook-form';
import { toNestError } from '@hookform/resolvers';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import type { ValidationError } from 'joi';
import { Resolver } from './types';

Expand Down Expand Up @@ -58,17 +58,24 @@ export const joiResolver: Resolver =
}
}

if (result.error) {
return {
values: {},
errors: toNestError(
parseErrorSchema(
result.error,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
options,
),
};
}

options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return {
values: result.error ? {} : result.value,
errors: result.error
? toNestError(
parseErrorSchema(
result.error,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
options,
)
: {},
errors: {},
values: result.value,
};
};
15 changes: 15 additions & 0 deletions nope/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,19 @@ test("form's native validation with Nope", async () => {
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
expect(passwordField.validity.valid).toBe(false);
expect(passwordField.validationMessage).toBe(PASSWORD_REQUIRED_MESSAGE);

await act(async () => {
user.type(screen.getByPlaceholderText(/username/i), 'joe');
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('');
});
12 changes: 8 additions & 4 deletions nope/src/nope.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FieldErrors } from 'react-hook-form';
import { toNestError } from '@hookform/resolvers';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import type { ShapeErrors } from 'nope-validator/lib/cjs/types';
import type { Resolver } from './types';

Expand Down Expand Up @@ -36,7 +36,11 @@ export const nopeResolver: Resolver =
| ShapeErrors
| undefined;

return result
? { values: {}, errors: toNestError(parseErrors(result), options) }
: { values, errors: {} };
if (result) {
return { values: {}, errors: toNestError(parseErrors(result), options) };
}

options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return { values, errors: {} };
};
42 changes: 0 additions & 42 deletions src/__tests__/__snapshots__/toNestObject.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@

exports[`transforms flat object to nested object 1`] = `
Object {
"n": Object {
"test": Object {
"message": "third message",
"ref": Object {
"reportValidity": [MockFunction],
"setCustomValidity": [MockFunction],
},
"type": "rd",
},
},
"name": Object {
"message": "first message",
"ref": Object {
Expand All @@ -34,38 +24,6 @@ Object {

exports[`transforms flat object to nested object and shouldUseNativeValidation: true 1`] = `
Object {
"n": Object {
"test": Object {
"message": "third message",
"ref": Object {
"reportValidity": [MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
"setCustomValidity": [MockFunction] {
"calls": Array [
Array [
"third message",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
},
"type": "rd",
},
},
"name": Object {
"message": "first message",
"ref": Object {
Expand Down
19 changes: 0 additions & 19 deletions src/__tests__/toNestObject.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Field, FieldError, InternalFieldName } from 'react-hook-form';
import { toNestError } from '../toNestError';

const flatObject: Record<string, FieldError> = {
name: { type: 'st', message: 'first message' },
'test.0.name': { type: 'nd', message: 'second message' },
'n.test': { type: 'rd', message: 'third message' },
};

const fields = {
Expand All @@ -15,14 +13,6 @@ const fields = {
setCustomValidity: jest.fn(),
},
},
n: {
test: {
ref: {
reportValidity: jest.fn(),
setCustomValidity: jest.fn(),
},
},
},
unused: {
ref: { name: 'unusedRef' },
},
Expand All @@ -47,13 +37,4 @@ test('transforms flat object to nested object and shouldUseNativeValidation: tru
expect(
(fields.name.ref as HTMLInputElement).setCustomValidity,
).toHaveBeenCalledWith(flatObject.name.message);

// @ts-expect-error
expect(fields.n?.test.ref.reportValidity).toHaveBeenCalledTimes(1);
// @ts-expect-error
expect(fields.n.test.ref.setCustomValidity).toHaveBeenCalledTimes(1);
// @ts-expect-error
expect(fields.n.test.ref.setCustomValidity).toHaveBeenCalledWith(
flatObject['n.test'].message,
);
});
42 changes: 42 additions & 0 deletions src/__tests__/validateFieldsNatively.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Field, FieldError, InternalFieldName } from 'react-hook-form';
import { validateFieldsNatively } from '../validateFieldsNatively';

const flatObject: Record<string, FieldError> = {
name: { type: 'st', message: 'first message' },
};

const fields = {
name: {
ref: {
reportValidity: jest.fn(),
setCustomValidity: jest.fn(),
},
},
nd: {
ref: {
reportValidity: jest.fn(),
setCustomValidity: jest.fn(),
},
},
} as any as Record<InternalFieldName, Field['_f']>;

test('validates natively fields', () => {
validateFieldsNatively(flatObject, {
fields,
shouldUseNativeValidation: true,
});

expect(
(fields.name.ref as HTMLInputElement).setCustomValidity,
).toHaveBeenCalledWith(flatObject.name.message);
expect(
(fields.name.ref as HTMLInputElement).reportValidity,
).toHaveBeenCalledTimes(1);

expect(
(fields.nd.ref as HTMLInputElement).setCustomValidity,
).toHaveBeenCalledWith('');
expect(
(fields.nd.ref as HTMLInputElement).reportValidity,
).toHaveBeenCalledTimes(1);
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './toNestError';
export * from './validateFieldsNatively';
Loading

0 comments on commit 7bfbb40

Please sign in to comment.