Skip to content

Commit

Permalink
Support Yup ^0.32.0 (#92)
Browse files Browse the repository at this point in the history
* test: restoreMocks: true

* chore: update yup

* chore: update Yup

* chore: use AnyObjectSchema instead of ObjectSchema

* revert: test description

* chore: support last yup version

* test: update tests

* chore: improve typings
  • Loading branch information
jorisre authored Dec 21, 2020
1 parent 76c68cc commit 39731b3
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 92 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
tsconfig: 'tsconfig.jest.json',
},
},
restoreMocks: true,
testMatch: ['**/?(*.)+(spec|test).ts?(x)'],
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
moduleFileExtensions: ['ts', 'tsx', 'js'],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@
"rollup-plugin-typescript2": "^0.29.0",
"superstruct": "^0.13.1",
"ts-jest": "^26.4.4",
"yup": "^0.32.8",
"typescript": "^4.1.3",
"vest": "^2.2.3",
"yup": "^0.31.0",
"zod": "^1.11.11"
},
"peerDependencies": {
Expand Down
12 changes: 12 additions & 0 deletions src/__snapshots__/yup.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,15 @@ Object {
"values": Object {},
}
`;

exports[`yupResolver should pass down the yup context 1`] = `
Object {
"errors": Object {
"name": Object {
"message": "name must be at least 6 characters",
"type": "min",
},
},
"values": Object {},
}
`;
151 changes: 80 additions & 71 deletions src/yup.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
import * as yup from 'yup';
import { yupResolver } from './yup';

Expand Down Expand Up @@ -42,7 +42,7 @@ const errors = {
],
};

const schema = yup.object().shape({
const schema = yup.object({
name: yup.string().required(),
age: yup.number().required().positive().integer(),
email: yup.string().email(),
Expand Down Expand Up @@ -72,42 +72,43 @@ describe('yupResolver', () => {
it('should get values', async () => {
const data = {
name: 'jimmy',
age: '24',
age: 24,
email: 'jimmy@mail.com',
password: '[}tehk6Uor',
createdOn: '2014-09-23T19:25:25Z',
foo: [{ yup: true }],
website: 'https://react-hook-form.com/',
createdOn: new Date('2014-09-23T19:25:25Z'),
foo: [{ loose: true }],
};
expect(await yupResolver(schema)(data)).toEqual({
errors: {},
values: {
name: 'jimmy',
age: 24,
password: '[}tehk6Uor',
foo: [{ yup: true }],
createdOn: new Date('2014-09-23T19:25:25Z'),
},
values: data,
});
});

it('should pass down the yup context', async () => {
const data = { name: 'eric' };
const context = { min: true };
const schemaWithContext = yup.object().shape({
const schemaWithContext = yup.object({
name: yup
.string()
.required()
.when('$min', (min: boolean, schema: yup.StringSchema) => {
return min ? schema.min(6) : schema;
}),
});
schemaWithContext.validate = jest.fn().mockResolvedValue({});
await yupResolver(schemaWithContext)(data, context);
expect(schemaWithContext.validate).toHaveBeenCalled();
expect(schemaWithContext.validate).toHaveBeenCalledWith(data, {
abortEarly: false,
context,
});
(schemaWithContext.validate as jest.Mock).mockClear();

const schemaSpyValidate = jest.spyOn(schemaWithContext, 'validate');

const output = await yupResolver(schemaWithContext)(data, context);
expect(schemaSpyValidate).toHaveBeenCalledTimes(1);
expect(schemaSpyValidate).toHaveBeenCalledWith(
data,
expect.objectContaining({
abortEarly: false,
context,
}),
);
expect(output).toMatchSnapshot();
});

describe('errors', () => {
Expand All @@ -119,26 +120,33 @@ describe('yupResolver', () => {
createdOn: null,
foo: [{ loose: null }],
};
const resolve = await yupResolver(schema)(data, {}, true);
expect(resolve).toMatchSnapshot();
expect(resolve.errors['foo'][0]['loose']).toBeDefined();
expect(resolve.errors['foo'][0]['loose'].types).toMatchInlineSnapshot(`

const output = await yupResolver(schema)(
// @ts-expect-error
data,
{},
true,
);
expect(output).toMatchSnapshot();
expect(output.errors['foo']?.[0]?.['loose']).toBeDefined();
expect(output.errors['foo']?.[0]?.['loose']?.types)
.toMatchInlineSnapshot(`
Object {
"typeError": "foo[0].loose must be a \`boolean\` type, but the final value was: \`null\`.
If \\"null\\" is intended as an empty value be sure to mark the schema as \`.nullable()\`",
}
`);
expect(resolve.errors.age.types).toMatchInlineSnapshot(`
expect(output.errors.age?.types).toMatchInlineSnapshot(`
Object {
"typeError": "age must be a \`number\` type, but the final value was: \`NaN\` (cast from the value \`\\"test\\"\`).",
}
`);
expect(resolve.errors.createdOn.types).toMatchInlineSnapshot(`
expect(output.errors.createdOn?.types).toMatchInlineSnapshot(`
Object {
"typeError": "createdOn must be a \`date\` type, but the final value was: \`Invalid Date\`.",
}
`);
expect(resolve.errors.password.types).toMatchInlineSnapshot(`
expect(output.errors.password?.types).toMatchInlineSnapshot(`
Object {
"matches": Array [
"Lowercase",
Expand All @@ -159,12 +167,13 @@ describe('yupResolver', () => {
createdOn: null,
foo: [{ loose: null }],
};
const resolve = await yupResolver(schema)(data);
expect(await yupResolver(schema)(data)).toMatchSnapshot();
expect(resolve.errors['foo[0].loose']).toBeUndefined();
expect(resolve.errors.age.types).toBeUndefined();
expect(resolve.errors.createdOn.types).toBeUndefined();
expect(resolve.errors.password.types).toBeUndefined();

// @ts-expect-error
const output = await yupResolver(schema)(data);
expect(output).toMatchSnapshot();
expect(output.errors.age?.types).toBeUndefined();
expect(output.errors.createdOn?.types).toBeUndefined();
expect(output.errors.password?.types).toBeUndefined();
});

it('should get error if yup errors has no inner errors', async () => {
Expand All @@ -174,10 +183,12 @@ describe('yupResolver', () => {
createdOn: null,
foo: [{ loose: null }],
};
const resolve = await yupResolver(schema, {
const output = await yupResolver(schema, {
abortEarly: true,
})(data);
expect(resolve.errors).toMatchInlineSnapshot(`
// @ts-expect-error
})(data, undefined, true);

expect(output.errors).toMatchInlineSnapshot(`
Object {
"createdOn": Object {
"message": "createdOn must be a \`date\` type, but the final value was: \`Invalid Date\`.",
Expand All @@ -192,35 +203,35 @@ describe('yupResolver', () => {
const schemaWithContext = yup.object().shape({
name: yup.string().required(),
});
schemaWithContext.validate = jest.fn().mockRejectedValue({

jest.spyOn(schemaWithContext, 'validate').mockRejectedValueOnce({
inner: [{ path: '', message: 'error1', type: 'required' }],
} as yup.ValidationError);
const result = await yupResolver(schemaWithContext)(data);
expect(result).toMatchSnapshot();
});

// @ts-expect-error
const output = await yupResolver(schemaWithContext)(data);
expect(output).toMatchSnapshot();
});
});
});

describe('validateWithSchema', () => {
it('should return undefined when no error reported', async () => {
expect(
await yupResolver({
validate: () => {
throw errors;
},
} as any)({}),
).toMatchSnapshot();
const schema = yup.object();
jest.spyOn(schema, 'validate').mockRejectedValueOnce(errors);

expect(await yupResolver(schema)({})).toMatchSnapshot();
});

it('should return empty object when validate pass', async () => {
expect(
await yupResolver({
validate: () => new Promise((resolve) => resolve(undefined)),
} as any)({}),
).toEqual({
errors: {},
values: undefined,
});
const schema = yup.object();

expect(await yupResolver(schema)({})).toMatchInlineSnapshot(`
Object {
"errors": Object {},
"values": Object {},
}
`);
});

it('should return an error based on the user context', async () => {
Expand All @@ -233,6 +244,8 @@ describe('validateWithSchema', () => {
return min ? schema.min(6) : schema;
}),
});

// @ts-expect-error
expect(await yupResolver(schemaWithContext)(data, { min: true }))
.toMatchInlineSnapshot(`
Object {
Expand All @@ -248,29 +261,23 @@ describe('validateWithSchema', () => {
});

it('should show a warning log if yup context is used instead only on dev environment', async () => {
console.warn = jest.fn();
jest.spyOn(console, 'warn').mockImplementation(jest.fn);
process.env.NODE_ENV = 'development';
await yupResolver(
{} as any,
{ context: { noContext: true } } as yup.ValidateOptions,
)({});

await yupResolver(yup.object(), { context: { noContext: true } })({});
expect(console.warn).toHaveBeenCalledWith(
"You should not used the yup options context. Please, use the 'useForm' context object instead",
);
process.env.NODE_ENV = 'test';
(console.warn as jest.Mock).mockClear();
});

it('should not show warning log if yup context is used instead only on production environment', async () => {
console.warn = jest.fn();
jest.spyOn(console, 'warn').mockImplementation(jest.fn);
process.env.NODE_ENV = 'production';
await yupResolver(
{} as any,
{ context: { noContext: true } } as yup.ValidateOptions,
)({});

await yupResolver(yup.object(), { context: { noContext: true } })({});
expect(console.warn).not.toHaveBeenCalled();
process.env.NODE_ENV = 'test';
(console.warn as jest.Mock).mockClear();
});

it('should return correct error message with using yup.test', async () => {
Expand All @@ -280,9 +287,11 @@ describe('validateWithSchema', () => {
name: yup.string(),
email: yup.string(),
})
.test('name', 'Email or name are required', function (value) {
return value && (value.name || value.email);
}),
.test(
'name',
'Email or name are required',
(value) => !!(value && (value.name || value.email)),
),
)({ name: '', email: '' });

expect(output).toEqual({
Expand Down
Loading

0 comments on commit 39731b3

Please sign in to comment.