main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
export * from './vest';
export * from './types';

View File

@@ -0,0 +1,8 @@
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[]>;

2
node_modules/@hookform/resolvers/vest/dist/vest.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import type { Resolver } from './types';
export declare const vestResolver: Resolver;

2
node_modules/@hookform/resolvers/vest/dist/vest.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
var e=require("@hookform/resolvers");function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=/*#__PURE__*/r(require("vest/promisify")),o=function(e,r){var t={};for(var o in e)t[o]||(t[o]={message:e[o][0],type:""}),r&&(t[o].types=e[o].reduce(function(e,r,t){return(e[t]=r)&&e},{}));return t};exports.vestResolver=function(r,s,i){return void 0===i&&(i={}),function(s,n,a){try{var u=function(r){return r.hasErrors()?{values:{},errors:e.toNestErrors(o(r.getErrors(),!a.shouldUseNativeValidation&&"all"===a.criteriaMode),a)}:(a.shouldUseNativeValidation&&e.validateFieldsNatively({},a),{values:s,errors:{}})};return Promise.resolve("sync"===i.mode?u(r(s,a.names,n)):Promise.resolve(t.default(r)(s,a.names,n)).then(u))}catch(e){return Promise.reject(e)}}};
//# sourceMappingURL=vest.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vest.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { Resolver, VestErrors } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver =\n (schema, _, resolverOptions = {}) =>\n async (values, context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values, options.names, context)\n : await promisify(schema)(values, options.names, context);\n\n if (result.hasErrors()) {\n return {\n values: {},\n errors: toNestErrors(\n parseErrorSchema(\n result.getErrors(),\n !options.shouldUseNativeValidation &&\n options.criteriaMode === 'all',\n ),\n options,\n ),\n };\n }\n\n options.shouldUseNativeValidation && validateFieldsNatively({}, options);\n\n return { values, errors: {} };\n };\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","schema","_","resolverOptions","values","context","options","_temp2","result","hasErrors","toNestErrors","getErrors","shouldUseNativeValidation","criteriaMode","validateFieldsNatively","Promise","resolve","mode","names","promisify","then","e","reject"],"mappings":"6JAKMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,CAAE,EAC7C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,GAAU,OAACD,EAAIC,GAASL,IAAYI,CAAG,EACtD,CAAE,IAIR,OAAON,CACT,uBAGE,SAACQ,EAAQC,EAAGC,GACLC,YADKD,IAAAA,IAAAA,EAAkB,CAAE,GACzBC,SAAAA,EAAQC,EAASC,GAAW,IAAA,IAAAC,EAAA,SAC3BC,GAKN,OAAIA,EAAOC,YACF,CACLL,OAAQ,CAAE,EACVX,OAAQiB,EAAYA,aAClBpB,EACEkB,EAAOG,aACNL,EAAQM,2BACkB,QAAzBN,EAAQO,cAEZP,KAKNA,EAAQM,2BAA6BE,EAAAA,uBAAuB,CAAA,EAAIR,GAEzD,CAAEF,OAAAA,EAAQX,OAAQ,CAAA,GAAK,EApBG,OAAAsB,QAAAC,QAAN,SAAzBb,EAAgBc,KAAeV,EAC3BN,EAAOG,EAAQE,EAAQY,MAAOb,IAAQU,QAAAC,QAChCG,EAAS,QAAClB,EAAVkB,CAAkBf,EAAQE,EAAQY,MAAOb,IAAQe,KAAAb,GAmB/D,CAAC,MAAAc,GAAAN,OAAAA,QAAAO,OAAAD,EAAA,CAAA,CAAA"}

2
node_modules/@hookform/resolvers/vest/dist/vest.mjs generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import{toNestErrors as r,validateFieldsNatively as e}from"@hookform/resolvers";import o from"vest/promisify";var t=function(r,e){var o={};for(var t in r)o[t]||(o[t]={message:r[t][0],type:""}),e&&(o[t].types=r[t].reduce(function(r,e,o){return(r[o]=e)&&r},{}));return o},s=function(s,n,i){return void 0===i&&(i={}),function(n,a,u){try{var m=function(o){return o.hasErrors()?{values:{},errors:r(t(o.getErrors(),!u.shouldUseNativeValidation&&"all"===u.criteriaMode),u)}:(u.shouldUseNativeValidation&&e({},u),{values:n,errors:{}})};return Promise.resolve("sync"===i.mode?m(s(n,u.names,a)):Promise.resolve(o(s)(n,u.names,a)).then(m))}catch(r){return Promise.reject(r)}}};export{s as vestResolver};
//# sourceMappingURL=vest.module.js.map

View File

@@ -0,0 +1,2 @@
import{toNestErrors as r,validateFieldsNatively as e}from"@hookform/resolvers";import s from"vest/promisify";const o=(r,e)=>{const s={};for(const o in r)s[o]||(s[o]={message:r[o][0],type:""}),e&&(s[o].types=r[o].reduce((r,e,s)=>(r[s]=e)&&r,{}));return s},t=(t,a,i={})=>async(a,n,m)=>{const l="sync"===i.mode?t(a,m.names,n):await s(t)(a,m.names,n);return l.hasErrors()?{values:{},errors:r(o(l.getErrors(),!m.shouldUseNativeValidation&&"all"===m.criteriaMode),m)}:(m.shouldUseNativeValidation&&e({},m),{values:a,errors:{}})};export{t as vestResolver};
//# sourceMappingURL=vest.modern.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vest.modern.mjs","sources":["../src/vest.ts"],"sourcesContent":["import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { Resolver, VestErrors } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver =\n (schema, _, resolverOptions = {}) =>\n async (values, context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values, options.names, context)\n : await promisify(schema)(values, options.names, context);\n\n if (result.hasErrors()) {\n return {\n values: {},\n errors: toNestErrors(\n parseErrorSchema(\n result.getErrors(),\n !options.shouldUseNativeValidation &&\n options.criteriaMode === 'all',\n ),\n options,\n ),\n };\n }\n\n options.shouldUseNativeValidation && validateFieldsNatively({}, options);\n\n return { values, errors: {} };\n };\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","vestResolver","schema","_","resolverOptions","async","values","context","options","result","mode","names","promisify","hasErrors","toNestErrors","getErrors","shouldUseNativeValidation","criteriaMode","validateFieldsNatively"],"mappings":"6GAKA,MAAMA,EAAmBA,CACvBC,EACAC,KAEA,MAAMC,EAAqC,GAC3C,IAAK,MAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,CAACC,EAAKJ,EAASK,KAAWD,EAAIC,GAASL,IAAYI,EACnD,CAAA,IAIN,OAAON,GAGIQ,EACXA,CAACC,EAAQC,EAAGC,EAAkB,KAC9BC,MAAOC,EAAQC,EAASC,KACtB,MAAMC,EACqB,SAAzBL,EAAgBM,KACZR,EAAOI,EAAQE,EAAQG,MAAOJ,SACxBK,EAAUV,EAAVU,CAAkBN,EAAQE,EAAQG,MAAOJ,GAErD,OAAIE,EAAOI,YACF,CACLP,OAAQ,CAAE,EACVb,OAAQqB,EACNxB,EACEmB,EAAOM,aACNP,EAAQQ,2BACkB,QAAzBR,EAAQS,cAEZT,KAKNA,EAAQQ,2BAA6BE,EAAuB,GAAIV,GAEzD,CAAEF,SAAQb,OAAQ,CAAA"}

View File

@@ -0,0 +1,2 @@
import{toNestErrors as r,validateFieldsNatively as e}from"@hookform/resolvers";import o from"vest/promisify";var t=function(r,e){var o={};for(var t in r)o[t]||(o[t]={message:r[t][0],type:""}),e&&(o[t].types=r[t].reduce(function(r,e,o){return(r[o]=e)&&r},{}));return o},s=function(s,n,i){return void 0===i&&(i={}),function(n,a,u){try{var m=function(o){return o.hasErrors()?{values:{},errors:r(t(o.getErrors(),!u.shouldUseNativeValidation&&"all"===u.criteriaMode),u)}:(u.shouldUseNativeValidation&&e({},u),{values:n,errors:{}})};return Promise.resolve("sync"===i.mode?m(s(n,u.names,a)):Promise.resolve(o(s)(n,u.names,a)).then(m))}catch(r){return Promise.reject(r)}}};export{s as vestResolver};
//# sourceMappingURL=vest.module.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vest.module.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { Resolver, VestErrors } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver =\n (schema, _, resolverOptions = {}) =>\n async (values, context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values, options.names, context)\n : await promisify(schema)(values, options.names, context);\n\n if (result.hasErrors()) {\n return {\n values: {},\n errors: toNestErrors(\n parseErrorSchema(\n result.getErrors(),\n !options.shouldUseNativeValidation &&\n options.criteriaMode === 'all',\n ),\n options,\n ),\n };\n }\n\n options.shouldUseNativeValidation && validateFieldsNatively({}, options);\n\n return { values, errors: {} };\n };\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","vestResolver","schema","_","resolverOptions","values","context","options","_temp2","result","hasErrors","toNestErrors","getErrors","shouldUseNativeValidation","criteriaMode","validateFieldsNatively","Promise","resolve","mode","names","promisify","then","e","reject"],"mappings":"6GAKA,IAAMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,CAAE,EAC7C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,GAAU,OAACD,EAAIC,GAASL,IAAYI,CAAG,EACtD,CAAE,IAIR,OAAON,CACT,EAEaQ,EACX,SAACC,EAAQC,EAAGC,GACLC,YADKD,IAAAA,IAAAA,EAAkB,CAAE,GACzBC,SAAAA,EAAQC,EAASC,GAAW,IAAA,IAAAC,EAAA,SAC3BC,GAKN,OAAIA,EAAOC,YACF,CACLL,OAAQ,CAAE,EACVZ,OAAQkB,EACNrB,EACEmB,EAAOG,aACNL,EAAQM,2BACkB,QAAzBN,EAAQO,cAEZP,KAKNA,EAAQM,2BAA6BE,EAAuB,CAAA,EAAIR,GAEzD,CAAEF,OAAAA,EAAQZ,OAAQ,CAAA,GAAK,EApBG,OAAAuB,QAAAC,QAAN,SAAzBb,EAAgBc,KAAeV,EAC3BN,EAAOG,EAAQE,EAAQY,MAAOb,IAAQU,QAAAC,QAChCG,EAAUlB,EAAVkB,CAAkBf,EAAQE,EAAQY,MAAOb,IAAQe,KAAAb,GAmB/D,CAAC,MAAAc,GAAAN,OAAAA,QAAAO,OAAAD,EAAA,CAAA,CAAA"}

View File

@@ -0,0 +1,2 @@
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@hookform/resolvers"),require("vest/promisify")):"function"==typeof define&&define.amd?define(["exports","@hookform/resolvers","vest/promisify"],r):r((e||self).hookformResolversVest={},e.hookformResolvers,e.promisify)}(this,function(e,r,o){function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=/*#__PURE__*/t(o),i=function(e,r){var o={};for(var t in e)o[t]||(o[t]={message:e[t][0],type:""}),r&&(o[t].types=e[t].reduce(function(e,r,o){return(e[o]=r)&&e},{}));return o};e.vestResolver=function(e,o,t){return void 0===t&&(t={}),function(o,n,f){try{var u=function(e){return e.hasErrors()?{values:{},errors:r.toNestErrors(i(e.getErrors(),!f.shouldUseNativeValidation&&"all"===f.criteriaMode),f)}:(f.shouldUseNativeValidation&&r.validateFieldsNatively({},f),{values:o,errors:{}})};return Promise.resolve("sync"===t.mode?u(e(o,f.names,n)):Promise.resolve(s.default(e)(o,f.names,n)).then(u))}catch(e){return Promise.reject(e)}}}});
//# sourceMappingURL=vest.umd.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"vest.umd.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { Resolver, VestErrors } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver =\n (schema, _, resolverOptions = {}) =>\n async (values, context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values, options.names, context)\n : await promisify(schema)(values, options.names, context);\n\n if (result.hasErrors()) {\n return {\n values: {},\n errors: toNestErrors(\n parseErrorSchema(\n result.getErrors(),\n !options.shouldUseNativeValidation &&\n options.criteriaMode === 'all',\n ),\n options,\n ),\n };\n }\n\n options.shouldUseNativeValidation && validateFieldsNatively({}, options);\n\n return { values, errors: {} };\n };\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","schema","_","resolverOptions","values","context","options","_temp2","result","hasErrors","toNestErrors","getErrors","shouldUseNativeValidation","criteriaMode","validateFieldsNatively","Promise","resolve","mode","names","promisify","then","e","reject"],"mappings":"mdAKMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,CAAE,EAC7C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,GAAU,OAACD,EAAIC,GAASL,IAAYI,CAAG,EACtD,CAAE,IAIR,OAAON,CACT,iBAGE,SAACQ,EAAQC,EAAGC,GACLC,YADKD,IAAAA,IAAAA,EAAkB,CAAE,GACzBC,SAAAA,EAAQC,EAASC,GAAW,IAAA,IAAAC,EAAA,SAC3BC,GAKN,OAAIA,EAAOC,YACF,CACLL,OAAQ,CAAE,EACVX,OAAQiB,EAAYA,aAClBpB,EACEkB,EAAOG,aACNL,EAAQM,2BACkB,QAAzBN,EAAQO,cAEZP,KAKNA,EAAQM,2BAA6BE,EAAAA,uBAAuB,CAAA,EAAIR,GAEzD,CAAEF,OAAAA,EAAQX,OAAQ,CAAA,GAAK,EApBG,OAAAsB,QAAAC,QAAN,SAAzBb,EAAgBc,KAAeV,EAC3BN,EAAOG,EAAQE,EAAQY,MAAOb,IAAQU,QAAAC,QAChCG,EAAS,QAAClB,EAAVkB,CAAkBf,EAAQE,EAAQY,MAAOb,IAAQe,KAAAb,GAmB/D,CAAC,MAAAc,GAAAN,OAAAA,QAAAO,OAAAD,EAAA,CAAA,CAAA"}

18
node_modules/@hookform/resolvers/vest/package.json generated vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "@hookform/resolvers/vest",
"amdName": "hookformResolversVest",
"version": "1.0.0",
"private": true,
"description": "React Hook Form validation resolver: vest",
"main": "dist/vest.js",
"module": "dist/vest.module.js",
"umd:main": "dist/vest.umd.js",
"source": "src/index.ts",
"types": "dist/index.d.ts",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0",
"@hookform/resolvers": "^2.0.0",
"vest": ">=3.0.0"
}
}

View 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('');
});

View 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();
});

View 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',
},
};

View 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": {},
}
`;

View 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
node_modules/@hookform/resolvers/vest/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export * from './vest';
export * from './types';

30
node_modules/@hookform/resolvers/vest/src/types.ts generated vendored Normal file
View 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
node_modules/@hookform/resolvers/vest/src/vest.ts generated vendored Normal file
View 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: {} };
};