Save current BZZZ config-ui state before CHORUS branding update
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
21
install/config-ui/node_modules/@hookform/resolvers/LICENSE
generated
vendored
Normal file
21
install/config-ui/node_modules/@hookform/resolvers/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-present Beier(Bill) Luo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
794
install/config-ui/node_modules/@hookform/resolvers/README.md
generated
vendored
Normal file
794
install/config-ui/node_modules/@hookform/resolvers/README.md
generated
vendored
Normal file
@@ -0,0 +1,794 @@
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<a href="https://react-hook-form.com" title="React Hook Form - Simple React forms validation">
|
||||
<img src="https://raw.githubusercontent.com/bluebill1049/react-hook-form/master/docs/logo.png" alt="React Hook Form Logo - React hook custom hook for form validation" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p align="center">Performant, flexible and extensible forms with easy to use validation.</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://www.npmjs.com/package/@hookform/resolvers)
|
||||
[](https://www.npmjs.com/package/@hookform/resolvers)
|
||||
[](https://bundlephobia.com/result?p=@hookform/resolvers)
|
||||
|
||||
</div>
|
||||
|
||||
## Install
|
||||
|
||||
npm install @hookform/resolvers
|
||||
|
||||
## Links
|
||||
|
||||
- [React-hook-form validation resolver documentation ](https://react-hook-form.com/api/useform/#resolver)
|
||||
|
||||
### Supported resolvers
|
||||
|
||||
- [Install](#install)
|
||||
- [Links](#links)
|
||||
- [Supported resolvers](#supported-resolvers)
|
||||
- [API](#api)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Yup](#yup)
|
||||
- [Zod](#zod)
|
||||
- [Superstruct](#superstruct)
|
||||
- [Joi](#joi)
|
||||
- [Vest](#vest)
|
||||
- [Class Validator](#class-validator)
|
||||
- [io-ts](#io-ts)
|
||||
- [Nope](#nope)
|
||||
- [computed-types](#computed-types)
|
||||
- [typanion](#typanion)
|
||||
- [Ajv](#ajv)
|
||||
- [TypeBox](#typebox)
|
||||
- [With `ValueCheck`](#with-valuecheck)
|
||||
- [With `TypeCompiler`](#with-typecompiler)
|
||||
- [ArkType](#arktype)
|
||||
- [Valibot](#valibot)
|
||||
- [TypeSchema](#typeschema)
|
||||
- [effect-ts](#effect-ts)
|
||||
- [VineJS](#vinejs)
|
||||
- [fluentvalidation-ts](#fluentvalidation-ts)
|
||||
- [Backers](#backers)
|
||||
- [Sponsors](#sponsors)
|
||||
- [Contributors](#contributors)
|
||||
|
||||
## API
|
||||
|
||||
```
|
||||
type Options = {
|
||||
mode: 'async' | 'sync',
|
||||
raw?: boolean
|
||||
}
|
||||
|
||||
resolver(schema: object, schemaOptions?: object, resolverOptions: Options)
|
||||
```
|
||||
|
||||
| | type | Required | Description |
|
||||
| --------------- | -------- | -------- | --------------------------------------------- |
|
||||
| schema | `object` | ✓ | validation schema |
|
||||
| schemaOptions | `object` | | validation library schema options |
|
||||
| resolverOptions | `object` | | resolver options, `async` is the default mode |
|
||||
|
||||
## Quickstart
|
||||
|
||||
### [Yup](https://github.com/jquense/yup)
|
||||
|
||||
Dead simple Object schema validation.
|
||||
|
||||
[](https://bundlephobia.com/result?p=yup)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const schema = yup
|
||||
.object()
|
||||
.shape({
|
||||
name: yup.string().required(),
|
||||
age: yup.number().required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
<input type="number" {...register('age')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Zod](https://github.com/vriad/zod)
|
||||
|
||||
TypeScript-first schema validation with static type inference
|
||||
|
||||
[](https://bundlephobia.com/result?p=zod)
|
||||
|
||||
> ⚠️ Example below uses the `valueAsNumber`, which requires `react-hook-form` v6.12.0 (released Nov 28, 2020) or later.
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, { message: 'Required' }),
|
||||
age: z.number().min(10),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
{errors.name?.message && <p>{errors.name?.message}</p>}
|
||||
<input type="number" {...register('age', { valueAsNumber: true })} />
|
||||
{errors.age?.message && <p>{errors.age?.message}</p>}
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Superstruct](https://github.com/ianstormtaylor/superstruct)
|
||||
|
||||
A simple and composable way to validate data in JavaScript (or TypeScript).
|
||||
|
||||
[](https://bundlephobia.com/result?p=superstruct)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { superstructResolver } from '@hookform/resolvers/superstruct';
|
||||
import { object, string, number } from 'superstruct';
|
||||
|
||||
const schema = object({
|
||||
name: string(),
|
||||
age: number(),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: superstructResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
<input type="number" {...register('age', { valueAsNumber: true })} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Joi](https://github.com/sideway/joi)
|
||||
|
||||
The most powerful data validation library for JS.
|
||||
|
||||
[](https://bundlephobia.com/result?p=joi)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import Joi from 'joi';
|
||||
|
||||
const schema = Joi.object({
|
||||
name: Joi.string().required(),
|
||||
age: Joi.number().required(),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: joiResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
<input type="number" {...register('age')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Vest](https://github.com/ealush/vest)
|
||||
|
||||
Vest 🦺 Declarative Validation Testing.
|
||||
|
||||
[](https://bundlephobia.com/result?p=vest)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { vestResolver } from '@hookform/resolvers/vest';
|
||||
import { create, test, enforce } from 'vest';
|
||||
|
||||
const validationSuite = create((data = {}) => {
|
||||
test('username', 'Username is required', () => {
|
||||
enforce(data.username).isNotEmpty();
|
||||
});
|
||||
|
||||
test('password', 'Password is required', () => {
|
||||
enforce(data.password).isNotEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit, errors } = useForm({
|
||||
resolver: vestResolver(validationSuite),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((data) => console.log(data))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Class Validator](https://github.com/typestack/class-validator)
|
||||
|
||||
Decorator-based property validation for classes.
|
||||
|
||||
[](https://bundlephobia.com/result?p=class-validator)
|
||||
|
||||
> ⚠️ Remember to add these options to your `tsconfig.json`!
|
||||
|
||||
```
|
||||
"strictPropertyInitialization": false,
|
||||
"experimentalDecorators": true
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
|
||||
import { Length, Min, IsEmail } from 'class-validator';
|
||||
|
||||
class User {
|
||||
@Length(2, 30)
|
||||
username: string;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
}
|
||||
|
||||
const resolver = classValidatorResolver(User);
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<User>({ resolver });
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((data) => console.log(data))}>
|
||||
<input type="text" {...register('username')} />
|
||||
{errors.username && <span>{errors.username.message}</span>}
|
||||
<input type="text" {...register('email')} />
|
||||
{errors.email && <span>{errors.email.message}</span>}
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [io-ts](https://github.com/gcanti/io-ts)
|
||||
|
||||
Validate your data with powerful decoders.
|
||||
|
||||
[](https://bundlephobia.com/result?p=io-ts)
|
||||
|
||||
```typescript jsx
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ioTsResolver } from '@hookform/resolvers/io-ts';
|
||||
import t from 'io-ts';
|
||||
// you don't have to use io-ts-types, but it's very useful
|
||||
import tt from 'io-ts-types';
|
||||
|
||||
const schema = t.type({
|
||||
username: t.string,
|
||||
age: tt.NumberFromString,
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: ioTsResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="number" {...register('age')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
### [Nope](https://github.com/bvego/nope-validator)
|
||||
|
||||
A small, simple, and fast JS validator
|
||||
|
||||
[](https://bundlephobia.com/result?p=nope-validator)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { nopeResolver } from '@hookform/resolvers/nope';
|
||||
import Nope from 'nope-validator';
|
||||
|
||||
const schema = Nope.object().shape({
|
||||
name: Nope.string().required(),
|
||||
age: Nope.number().required(),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: nopeResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
<input type="number" {...register('age')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [computed-types](https://github.com/neuledge/computed-types)
|
||||
|
||||
TypeScript-first schema validation with static type inference
|
||||
|
||||
[](https://bundlephobia.com/result?p=computed-types)
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { computedTypesResolver } from '@hookform/resolvers/computed-types';
|
||||
import Schema, { number, string } from 'computed-types';
|
||||
|
||||
const schema = Schema({
|
||||
username: string.min(1).error('username field is required'),
|
||||
age: number,
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: computedTypesResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
{errors.name?.message && <p>{errors.name?.message}</p>}
|
||||
<input type="number" {...register('age', { valueAsNumber: true })} />
|
||||
{errors.age?.message && <p>{errors.age?.message}</p>}
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [typanion](https://github.com/arcanis/typanion)
|
||||
|
||||
Static and runtime type assertion library with no dependencies
|
||||
|
||||
[](https://bundlephobia.com/result?p=typanion)
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { typanionResolver } from '@hookform/resolvers/typanion';
|
||||
import * as t from 'typanion';
|
||||
|
||||
const isUser = t.isObject({
|
||||
username: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
|
||||
age: t.applyCascade(t.isNumber(), [
|
||||
t.isInteger(),
|
||||
t.isInInclusiveRange(1, 100),
|
||||
]),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: typanionResolver(isUser),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('name')} />
|
||||
{errors.name?.message && <p>{errors.name?.message}</p>}
|
||||
<input type="number" {...register('age')} />
|
||||
{errors.age?.message && <p>{errors.age?.message}</p>}
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Ajv](https://github.com/ajv-validator/ajv)
|
||||
|
||||
The fastest JSON validator for Node.js and browser
|
||||
|
||||
[](https://bundlephobia.com/result?p=ajv)
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ajvResolver } from '@hookform/resolvers/ajv';
|
||||
|
||||
// must use `minLength: 1` to implement required field
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: 'username field is required' },
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: 'password field is required' },
|
||||
},
|
||||
},
|
||||
required: ['username', 'password'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: ajvResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((data) => console.log(data))}>
|
||||
<input {...register('username')} />
|
||||
{errors.username && <span>{errors.username.message}</span>}
|
||||
<input {...register('password')} />
|
||||
{errors.password && <span>{errors.password.message}</span>}
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [TypeBox](https://github.com/sinclairzx81/typebox)
|
||||
|
||||
JSON Schema Type Builder with Static Type Resolution for TypeScript
|
||||
|
||||
[](https://bundlephobia.com/result?p=@sinclair/typebox)
|
||||
|
||||
#### With `ValueCheck`
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { typeboxResolver } from '@hookform/resolvers/typebox';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
username: Type.String({ minLength: 1 }),
|
||||
password: Type.String({ minLength: 1 }),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### With `TypeCompiler`
|
||||
|
||||
A high-performance JIT of `TypeBox`, [read more](https://github.com/sinclairzx81/typebox#typecompiler)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { typeboxResolver } from '@hookform/resolvers/typebox';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler';
|
||||
|
||||
const schema = Type.Object({
|
||||
username: Type.String({ minLength: 1 }),
|
||||
password: Type.String({ minLength: 1 }),
|
||||
});
|
||||
|
||||
const typecheck = TypeCompiler.Compile(schema);
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: typeboxResolver(typecheck),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [ArkType](https://github.com/arktypeio/arktype)
|
||||
|
||||
TypeScript's 1:1 validator, optimized from editor to runtime
|
||||
|
||||
[](https://bundlephobia.com/result?p=arktype)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { arktypeResolver } from '@hookform/resolvers/arktype';
|
||||
import { type } from 'arktype';
|
||||
|
||||
const schema = type({
|
||||
username: 'string>1',
|
||||
password: 'string>1',
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: arktypeResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [Valibot](https://github.com/fabian-hiller/valibot)
|
||||
|
||||
The modular and type safe schema library for validating structural data
|
||||
|
||||
[](https://bundlephobia.com/result?p=valibot)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { valibotResolver } from '@hookform/resolvers/valibot';
|
||||
import * as v from 'valibot';
|
||||
|
||||
const schema = v.object({
|
||||
username: v.pipe(
|
||||
v.string('username is required'),
|
||||
v.minLength(3, 'Needs to be at least 3 characters'),
|
||||
v.endsWith('cool', 'Needs to end with `cool`'),
|
||||
),
|
||||
password: v.string('password is required'),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: valibotResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [TypeSchema](https://typeschema.com)
|
||||
|
||||
Universal adapter for schema validation, compatible with [any validation library](https://typeschema.com/#coverage)
|
||||
|
||||
[](https://bundlephobia.com/result?p=@typeschema/main)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { typeschemaResolver } from '@hookform/resolvers/typeschema';
|
||||
import * as z from 'zod';
|
||||
|
||||
// Use your favorite validation library
|
||||
const schema = z.object({
|
||||
username: z.string().min(1, { message: 'Required' }),
|
||||
password: z.number().min(1, { message: 'Required' }),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: typeschemaResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<input {...register('username')} />
|
||||
<input type="password" {...register('password')} />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### [effect-ts](https://github.com/Effect-TS/effect)
|
||||
|
||||
A powerful TypeScript framework that provides a fully-fledged functional effect system with a rich standard library.
|
||||
|
||||
[](https://bundlephobia.com/result?p=effect)
|
||||
|
||||
```typescript jsx
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { effectTsResolver } from '@hookform/resolvers/effect-ts';
|
||||
import { Schema } from 'effect';
|
||||
|
||||
const schema = Schema.Struct({
|
||||
username: Schema.String.pipe(
|
||||
Schema.nonEmpty({ message: () => 'username required' }),
|
||||
),
|
||||
password: Schema.String.pipe(
|
||||
Schema.nonEmpty({ message: () => 'password required' }),
|
||||
),
|
||||
});
|
||||
|
||||
type FormData = typeof schema.Type;
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
// provide generic if TS has issues inferring types
|
||||
} = useForm<FormData>({
|
||||
resolver: effectTsResolver(schema),
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### [VineJS](https://github.com/vinejs/vine)
|
||||
|
||||
VineJS is a form data validation library for Node.js
|
||||
|
||||
[](https://bundlephobia.com/result?p=@vinejs/vine)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { vineResolver } from '@hookform/resolvers/vine';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
const schema = vine.compile(
|
||||
vine.object({
|
||||
username: vine.string().minLength(1),
|
||||
password: vine.string().minLength(1),
|
||||
}),
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: vineResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### [fluentvalidation-ts](https://github.com/AlexJPotter/fluentvalidation-ts)
|
||||
|
||||
A TypeScript-first library for building strongly-typed validation rules
|
||||
|
||||
[](https://bundlephobia.com/result?p=@vinejs/vine)
|
||||
|
||||
```typescript jsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { fluentValidationResolver } from '@hookform/resolvers/fluentvalidation-ts';
|
||||
import { Validator } from 'fluentvalidation-ts';
|
||||
|
||||
class FormDataValidator extends Validator<FormData> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.ruleFor('username')
|
||||
.notEmpty()
|
||||
.withMessage('username is a required field');
|
||||
this.ruleFor('password')
|
||||
.notEmpty()
|
||||
.withMessage('password is a required field');
|
||||
}
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
resolver: fluentValidationResolver(new FormDataValidator()),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Backers
|
||||
|
||||
Thanks go to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)].
|
||||
|
||||
<a href="https://opencollective.com/react-hook-form#backers">
|
||||
<img src="https://opencollective.com/react-hook-form/backers.svg?width=950" />
|
||||
</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks go to these wonderful people! [[Become a contributor](CONTRIBUTING.md)].
|
||||
|
||||
<a href="https://github.com/react-hook-form/react-hook-form/graphs/contributors">
|
||||
<img src="https://opencollective.com/react-hook-form/contributors.svg?width=950" />
|
||||
</a>
|
||||
19
install/config-ui/node_modules/@hookform/resolvers/ajv/package.json
generated
vendored
Normal file
19
install/config-ui/node_modules/@hookform/resolvers/ajv/package.json
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/ajv",
|
||||
"amdName": "hookformResolversAjv",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: ajv",
|
||||
"main": "dist/ajv.js",
|
||||
"module": "dist/ajv.module.js",
|
||||
"umd:main": "dist/ajv.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",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-errors": "^3.0.0"
|
||||
}
|
||||
}
|
||||
94
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
94
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { JSONSchemaType } from 'ajv';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ajvResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
type FormData = { username: string; password: string };
|
||||
|
||||
const schema: JSONSchemaType<FormData> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: USERNAME_REQUIRED_MESSAGE },
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: PASSWORD_REQUIRED_MESSAGE },
|
||||
},
|
||||
},
|
||||
required: ['username', 'password'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: ajvResolver(schema),
|
||||
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 Ajv", 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_REQUIRED_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('');
|
||||
});
|
||||
65
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/Form.tsx
generated
vendored
Normal file
65
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { JSONSchemaType } from 'ajv';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ajvResolver } from '..';
|
||||
|
||||
type FormData = { username: string; password: string };
|
||||
|
||||
const schema: JSONSchemaType<FormData> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: 'username field is required' },
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
errorMessage: { minLength: 'password field is required' },
|
||||
},
|
||||
},
|
||||
required: ['username', 'password'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: ajvResolver(schema), // 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 Ajv 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 field is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password field is required/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
90
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
90
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import { JSONSchemaType } from 'ajv';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
interface Data {
|
||||
username: string;
|
||||
password: string;
|
||||
deepObject: { data: string; twoLayersDeep: { name: string } };
|
||||
}
|
||||
|
||||
export const schema: JSONSchemaType<Data> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
pattern: '.*[A-Z].*',
|
||||
errorMessage: {
|
||||
pattern: 'One uppercase character',
|
||||
},
|
||||
},
|
||||
deepObject: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: { type: 'string' },
|
||||
twoLayersDeep: {
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
additionalProperties: false,
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
required: ['data', 'twoLayersDeep'],
|
||||
},
|
||||
},
|
||||
required: ['username', 'password', 'deepObject'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
export const validData: Data = {
|
||||
username: 'jsun969',
|
||||
password: 'validPassword',
|
||||
deepObject: {
|
||||
twoLayersDeep: {
|
||||
name: 'deeper',
|
||||
},
|
||||
data: 'data',
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
username: '__',
|
||||
password: 'invalid-password',
|
||||
deepObject: {
|
||||
data: 233,
|
||||
twoLayersDeep: { name: 123 },
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidDataWithUndefined = {
|
||||
username: 'jsun969',
|
||||
password: undefined,
|
||||
deepObject: {
|
||||
twoLayersDeep: {
|
||||
name: 'deeper',
|
||||
},
|
||||
data: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
245
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/__snapshots__/ajv.ts.snap
generated
vendored
Normal file
245
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/__snapshots__/ajv.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ajvResolver > should return all the error messages from ajvResolver when requirement fails and validateAllFieldCriteria set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"message": "must have required property 'deepObject'",
|
||||
"ref": undefined,
|
||||
"type": "required",
|
||||
},
|
||||
"password": {
|
||||
"message": "must have required property 'password'",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "required",
|
||||
},
|
||||
"username": {
|
||||
"message": "must have required property 'username'",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "required",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return all the error messages from ajvResolver when requirement fails and validateAllFieldCriteria set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"message": "must have required property 'deepObject'",
|
||||
"ref": undefined,
|
||||
"type": "required",
|
||||
},
|
||||
"password": {
|
||||
"message": "must have required property 'password'",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "required",
|
||||
},
|
||||
"username": {
|
||||
"message": "must have required property 'username'",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "required",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return all the error messages from ajvResolver when some property is undefined and result will keep the input data structure 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "must have required property 'data'",
|
||||
"ref": undefined,
|
||||
"type": "required",
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "must have required property 'password'",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "required",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return all the error messages from ajvResolver when validation fails and validateAllFieldCriteria set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
"types": {
|
||||
"type": "must be string",
|
||||
},
|
||||
},
|
||||
"twoLayersDeep": {
|
||||
"name": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
"types": {
|
||||
"type": "must be string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "errorMessage",
|
||||
"types": {
|
||||
"errorMessage": "One uppercase character",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "must NOT have fewer than 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "minLength",
|
||||
"types": {
|
||||
"minLength": "must NOT have fewer than 3 characters",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return all the error messages from ajvResolver when validation fails and validateAllFieldCriteria set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
"types": {
|
||||
"type": "must be string",
|
||||
},
|
||||
},
|
||||
"twoLayersDeep": {
|
||||
"name": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
"types": {
|
||||
"type": "must be string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "errorMessage",
|
||||
"types": {
|
||||
"errorMessage": "One uppercase character",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "must NOT have fewer than 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "minLength",
|
||||
"types": {
|
||||
"minLength": "must NOT have fewer than 3 characters",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return single error message from ajvResolver when validation fails and validateAllFieldCriteria set to false 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
},
|
||||
"twoLayersDeep": {
|
||||
"name": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "errorMessage",
|
||||
},
|
||||
"username": {
|
||||
"message": "must NOT have fewer than 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "minLength",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ajvResolver > should return single error message from ajvResolver when validation fails and validateAllFieldCriteria set to false and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"deepObject": {
|
||||
"data": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
},
|
||||
"twoLayersDeep": {
|
||||
"name": {
|
||||
"message": "must be string",
|
||||
"ref": undefined,
|
||||
"type": "type",
|
||||
},
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "errorMessage",
|
||||
},
|
||||
"username": {
|
||||
"message": "must NOT have fewer than 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "minLength",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
103
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/ajv.ts
generated
vendored
Normal file
103
install/config-ui/node_modules/@hookform/resolvers/ajv/src/__tests__/ajv.ts
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import { ajvResolver } from '..';
|
||||
import {
|
||||
fields,
|
||||
invalidData,
|
||||
invalidDataWithUndefined,
|
||||
schema,
|
||||
validData,
|
||||
} from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('ajvResolver', () => {
|
||||
it('should return values from ajvResolver when validation pass', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toEqual({
|
||||
values: validData,
|
||||
errors: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return values from ajvResolver with `mode: sync` when validation pass', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(validData, undefined, { fields, shouldUseNativeValidation }),
|
||||
).toEqual({
|
||||
values: validData,
|
||||
errors: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return single error message from ajvResolver when validation fails and validateAllFieldCriteria set to false', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return single error message from ajvResolver when validation fails and validateAllFieldCriteria set to false and `mode: sync`', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(invalidData, undefined, { fields, shouldUseNativeValidation }),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from ajvResolver when validation fails and validateAllFieldCriteria set to true', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema)(
|
||||
invalidData,
|
||||
{},
|
||||
{ fields, criteriaMode: 'all', shouldUseNativeValidation },
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from ajvResolver when validation fails and validateAllFieldCriteria set to true and `mode: sync`', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema, undefined, { mode: 'sync' })(
|
||||
invalidData,
|
||||
{},
|
||||
{ fields, criteriaMode: 'all', shouldUseNativeValidation },
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from ajvResolver when requirement fails and validateAllFieldCriteria set to true', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema)({}, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from ajvResolver when requirement fails and validateAllFieldCriteria set to true and `mode: sync`', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema, undefined, { mode: 'sync' })({}, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the error messages from ajvResolver when some property is undefined and result will keep the input data structure', async () => {
|
||||
expect(
|
||||
await ajvResolver(schema, undefined, { mode: 'sync' })(
|
||||
invalidDataWithUndefined,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
88
install/config-ui/node_modules/@hookform/resolvers/ajv/src/ajv.ts
generated
vendored
Normal file
88
install/config-ui/node_modules/@hookform/resolvers/ajv/src/ajv.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import Ajv, { DefinedError } from 'ajv';
|
||||
import ajvErrors from 'ajv-errors';
|
||||
import { FieldError, appendErrors } from 'react-hook-form';
|
||||
import { Resolver } from './types';
|
||||
|
||||
const parseErrorSchema = (
|
||||
ajvErrors: DefinedError[],
|
||||
validateAllFieldCriteria: boolean,
|
||||
) => {
|
||||
// Ajv will return empty instancePath when require error
|
||||
ajvErrors.forEach((error) => {
|
||||
if (error.keyword === 'required') {
|
||||
error.instancePath += '/' + error.params.missingProperty;
|
||||
}
|
||||
});
|
||||
|
||||
return ajvErrors.reduce<Record<string, FieldError>>((previous, error) => {
|
||||
// `/deepObject/data` -> `deepObject.data`
|
||||
const path = error.instancePath.substring(1).replace(/\//g, '.');
|
||||
|
||||
if (!previous[path]) {
|
||||
previous[path] = {
|
||||
message: error.message,
|
||||
type: error.keyword,
|
||||
};
|
||||
}
|
||||
|
||||
if (validateAllFieldCriteria) {
|
||||
const types = previous[path].types;
|
||||
const messages = types && types[error.keyword];
|
||||
|
||||
previous[path] = appendErrors(
|
||||
path,
|
||||
validateAllFieldCriteria,
|
||||
previous,
|
||||
error.keyword,
|
||||
messages
|
||||
? ([] as string[]).concat(messages as string[], error.message || '')
|
||||
: error.message,
|
||||
) as FieldError;
|
||||
}
|
||||
|
||||
return previous;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const ajvResolver: Resolver =
|
||||
(schema, schemaOptions, resolverOptions = {}) =>
|
||||
async (values, _, options) => {
|
||||
const ajv = new Ajv(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
allErrors: true,
|
||||
validateSchema: true,
|
||||
},
|
||||
schemaOptions,
|
||||
),
|
||||
);
|
||||
|
||||
ajvErrors(ajv);
|
||||
|
||||
const validate = ajv.compile(
|
||||
Object.assign(
|
||||
{ $async: resolverOptions && resolverOptions.mode === 'async' },
|
||||
schema,
|
||||
),
|
||||
);
|
||||
|
||||
const valid = validate(values);
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return valid
|
||||
? { values, errors: {} }
|
||||
: {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrorSchema(
|
||||
validate.errors as DefinedError[],
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
};
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/ajv/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/ajv/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './ajv';
|
||||
export * from './types';
|
||||
12
install/config-ui/node_modules/@hookform/resolvers/ajv/src/types.ts
generated
vendored
Normal file
12
install/config-ui/node_modules/@hookform/resolvers/ajv/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as Ajv from 'ajv';
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
|
||||
export type Resolver = <T>(
|
||||
schema: Ajv.JSONSchemaType<T>,
|
||||
schemaOptions?: Ajv.Options,
|
||||
factoryOptions?: { mode?: 'async' | 'sync' },
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => Promise<ResolverResult<TFieldValues>>;
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/arktype/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/arktype/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/arktype",
|
||||
"amdName": "hookformResolversArktype",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: arktype",
|
||||
"main": "dist/arktype.js",
|
||||
"module": "dist/arktype.module.js",
|
||||
"umd:main": "dist/arktype.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",
|
||||
"arktype": "2.0.0-dev.14"
|
||||
}
|
||||
}
|
||||
82
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
82
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { type } from 'arktype';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { arktypeResolver } from '..';
|
||||
|
||||
const schema = type({
|
||||
username: 'string>1',
|
||||
password: 'string>1',
|
||||
});
|
||||
|
||||
type FormData = typeof schema.infer;
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: arktypeResolver(schema),
|
||||
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 Zod", 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 must be more than length 1',
|
||||
);
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe(
|
||||
'password must be more than length 1',
|
||||
);
|
||||
|
||||
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('');
|
||||
});
|
||||
56
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/Form.tsx
generated
vendored
Normal file
56
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { type } from 'arktype';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { arktypeResolver } from '..';
|
||||
|
||||
const schema = type({
|
||||
username: 'string>1',
|
||||
password: 'string>1',
|
||||
});
|
||||
|
||||
type FormData = typeof schema.infer & { unusedProperty: string };
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
resolver: arktypeResolver(schema), // 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 arkType 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 must be more than length 1 (was 0)'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('password must be more than length 1 (was 0)'),
|
||||
).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
65
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
65
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { type } from 'arktype';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = type({
|
||||
username: 'string>2',
|
||||
password: '/.*[A-Za-z].*/>8|/.*\\d.*/',
|
||||
repeatPassword: 'string>1',
|
||||
accessToken: 'string|number',
|
||||
birthYear: '1900<number<2013',
|
||||
email: 'email',
|
||||
tags: 'string[]',
|
||||
enabled: 'boolean',
|
||||
url: 'string>1',
|
||||
'like?': type({
|
||||
id: 'number',
|
||||
name: 'string>3',
|
||||
}).array(),
|
||||
dateStr: 'Date',
|
||||
});
|
||||
|
||||
export const validData: typeof schema.infer = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
accessToken: 'accessToken',
|
||||
url: 'https://react-hook-form.com/',
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
dateStr: new Date('2020-01-01'),
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z' }],
|
||||
url: 'abc',
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
467
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/__snapshots__/arktype.ts.snap
generated
vendored
Normal file
467
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/__snapshots__/arktype.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`arktypeResolver > should return a single error from arktypeResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"accessToken": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "a number or a string",
|
||||
"relativePath": [
|
||||
"accessToken",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "a number or a string",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"accessToken",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"accessToken",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"birthYear": ArkError {
|
||||
"code": "domain",
|
||||
"data": "birthYear",
|
||||
"description": "a number",
|
||||
"domain": "number",
|
||||
"input": {
|
||||
"code": "domain",
|
||||
"description": "a number",
|
||||
"domain": "number",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"birthYear",
|
||||
],
|
||||
"ref": undefined,
|
||||
"type": "domain",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"dateStr": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "a Date",
|
||||
"relativePath": [
|
||||
"dateStr",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "a Date",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"dateStr",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"dateStr",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"email": ArkError {
|
||||
"code": "pattern",
|
||||
"data": "",
|
||||
"description": "a valid email",
|
||||
"flags": "",
|
||||
"input": {
|
||||
"code": "pattern",
|
||||
"description": "a valid email",
|
||||
"flags": "",
|
||||
"rule": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"email",
|
||||
],
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"rule": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$",
|
||||
"type": "pattern",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"enabled": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "boolean",
|
||||
"relativePath": [
|
||||
"enabled",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "boolean",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"enabled",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"enabled",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": ArkError {
|
||||
"code": "domain",
|
||||
"data": "z",
|
||||
"description": "a number",
|
||||
"domain": "number",
|
||||
"input": {
|
||||
"code": "domain",
|
||||
"description": "a number",
|
||||
"domain": "number",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"like",
|
||||
0,
|
||||
"id",
|
||||
],
|
||||
"ref": undefined,
|
||||
"type": "domain",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"name": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"id": "z",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "a string",
|
||||
"relativePath": [
|
||||
"name",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "a string",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"like",
|
||||
0,
|
||||
"name",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"name",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": ArkError {
|
||||
"code": "union",
|
||||
"data": "___",
|
||||
"errors": [
|
||||
ArkError {
|
||||
"code": "pattern",
|
||||
"data": "___",
|
||||
"description": "matched by .*[A-Za-z].*",
|
||||
"input": {
|
||||
"code": "pattern",
|
||||
"description": "matched by .*[A-Za-z].*",
|
||||
"rule": ".*[A-Za-z].*",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"password",
|
||||
],
|
||||
"rule": ".*[A-Za-z].*",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
ArkError {
|
||||
"code": "pattern",
|
||||
"data": "___",
|
||||
"description": "matched by .*\\d.*",
|
||||
"input": {
|
||||
"code": "pattern",
|
||||
"description": "matched by .*\\d.*",
|
||||
"rule": ".*\\d.*",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"password",
|
||||
],
|
||||
"rule": ".*\\d.*",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
],
|
||||
"input": {
|
||||
"code": "union",
|
||||
"errors": [
|
||||
ArkError {
|
||||
"code": "pattern",
|
||||
"data": "___",
|
||||
"description": "matched by .*[A-Za-z].*",
|
||||
"input": {
|
||||
"code": "pattern",
|
||||
"description": "matched by .*[A-Za-z].*",
|
||||
"rule": ".*[A-Za-z].*",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"password",
|
||||
],
|
||||
"rule": ".*[A-Za-z].*",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
ArkError {
|
||||
"code": "pattern",
|
||||
"data": "___",
|
||||
"description": "matched by .*\\d.*",
|
||||
"input": {
|
||||
"code": "pattern",
|
||||
"description": "matched by .*\\d.*",
|
||||
"rule": ".*\\d.*",
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"password",
|
||||
],
|
||||
"rule": ".*\\d.*",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
],
|
||||
},
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"password",
|
||||
],
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "union",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"repeatPassword": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "a string",
|
||||
"relativePath": [
|
||||
"repeatPassword",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "a string",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"repeatPassword",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"repeatPassword",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"tags": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "an array",
|
||||
"relativePath": [
|
||||
"tags",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "an array",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"tags",
|
||||
],
|
||||
"ref": undefined,
|
||||
"relativePath": [
|
||||
"tags",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
"username": ArkError {
|
||||
"code": "required",
|
||||
"data": {
|
||||
"birthYear": "birthYear",
|
||||
"email": "",
|
||||
"like": [
|
||||
{
|
||||
"id": "z",
|
||||
},
|
||||
],
|
||||
"password": "___",
|
||||
"url": "abc",
|
||||
},
|
||||
"input": {
|
||||
"code": "required",
|
||||
"missingValueDescription": "a string",
|
||||
"relativePath": [
|
||||
"username",
|
||||
],
|
||||
},
|
||||
"missingValueDescription": "a string",
|
||||
"nodeConfig": {
|
||||
"actual": [Function],
|
||||
"description": [Function],
|
||||
"expected": [Function],
|
||||
"message": [Function],
|
||||
"problem": [Function],
|
||||
},
|
||||
"path": [
|
||||
"username",
|
||||
],
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"relativePath": [
|
||||
"username",
|
||||
],
|
||||
"type": "required",
|
||||
Symbol(ArkTypeInternalKind): "error",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
26
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/arktype.ts
generated
vendored
Normal file
26
install/config-ui/node_modules/@hookform/resolvers/arktype/src/__tests__/arktype.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { arktypeResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('arktypeResolver', () => {
|
||||
it('should return values from arktypeResolver when validation pass & raw=true', async () => {
|
||||
const result = await arktypeResolver(schema, undefined, {
|
||||
raw: true,
|
||||
})(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from arktypeResolver when validation fails', async () => {
|
||||
const result = await arktypeResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
31
install/config-ui/node_modules/@hookform/resolvers/arktype/src/arktype.ts
generated
vendored
Normal file
31
install/config-ui/node_modules/@hookform/resolvers/arktype/src/arktype.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import { ArkErrors } from 'arktype';
|
||||
import { FieldError, FieldErrors } from 'react-hook-form';
|
||||
import type { Resolver } from './types';
|
||||
|
||||
const parseErrorSchema = (e: ArkErrors): Record<string, FieldError> => {
|
||||
// copy code to type to match FieldError shape
|
||||
e.forEach((e) => Object.assign(e, { type: e.code }));
|
||||
// need to cast here because TS doesn't understand we added the type field
|
||||
return e.byPath as never;
|
||||
};
|
||||
|
||||
export const arktypeResolver: Resolver =
|
||||
(schema, _schemaOptions, resolverOptions = {}) =>
|
||||
(values, _, options) => {
|
||||
const out = schema(values);
|
||||
|
||||
if (out instanceof ArkErrors) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(parseErrorSchema(out), options),
|
||||
};
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
errors: {} as FieldErrors,
|
||||
values: resolverOptions.raw ? values : out,
|
||||
};
|
||||
};
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/arktype/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/arktype/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './arktype';
|
||||
export * from './types';
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/arktype/src/types.ts
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/arktype/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Type } from 'arktype';
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
|
||||
export type Resolver = <T extends Type<any>>(
|
||||
schema: T,
|
||||
schemaOptions?: undefined,
|
||||
factoryOptions?: {
|
||||
/**
|
||||
* Return the raw input values rather than the parsed values.
|
||||
* @default false
|
||||
*/
|
||||
raw?: boolean;
|
||||
},
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => ResolverResult<TFieldValues>;
|
||||
19
install/config-ui/node_modules/@hookform/resolvers/class-validator/package.json
generated
vendored
Normal file
19
install/config-ui/node_modules/@hookform/resolvers/class-validator/package.json
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/class-validator",
|
||||
"amdName": "hookformResolversClassValidator",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: class-validator",
|
||||
"main": "dist/class-validator.js",
|
||||
"module": "dist/class-validator.module.js",
|
||||
"umd:main": "dist/class-validator.umd.js",
|
||||
"source": "src/index.ts",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-hook-form": ">=6.6.0",
|
||||
"@hookform/resolvers": ">=2.0.0",
|
||||
"class-transformer": "^0.4.0",
|
||||
"class-validator": "^0.12.0"
|
||||
}
|
||||
}
|
||||
79
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
79
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
import React from 'react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { classValidatorResolver } from '..';
|
||||
|
||||
class Schema {
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: SubmitHandler<Schema>;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<Schema>({
|
||||
resolver: classValidatorResolver(Schema),
|
||||
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 Class Validator", 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 should not be empty');
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe('password should not be empty');
|
||||
|
||||
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('');
|
||||
});
|
||||
53
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/Form.tsx
generated
vendored
Normal file
53
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
import React from 'react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { classValidatorResolver } from '..';
|
||||
|
||||
class Schema {
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: SubmitHandler<Schema>;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<Schema>({
|
||||
resolver: classValidatorResolver(Schema),
|
||||
});
|
||||
|
||||
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 Class Validator 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 should not be empty/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password should not be empty/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
88
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
88
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'reflect-metadata';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
Length,
|
||||
Matches,
|
||||
Max,
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
class Like {
|
||||
@IsNotEmpty()
|
||||
id: number;
|
||||
|
||||
@Length(4)
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class Schema {
|
||||
@Matches(/^\w+$/)
|
||||
@Length(3, 30)
|
||||
username: string;
|
||||
|
||||
@Matches(/^[a-zA-Z0-9]{3,30}/)
|
||||
password: string;
|
||||
|
||||
@Min(1900)
|
||||
@Max(2013)
|
||||
birthYear: number;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
accessToken: string;
|
||||
|
||||
tags: string[];
|
||||
|
||||
enabled: boolean;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => Like)
|
||||
like: Like[];
|
||||
}
|
||||
|
||||
export const validData: Schema = {
|
||||
username: 'Doe',
|
||||
password: 'Password123',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
accessToken: 'accessToken',
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z' }],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
242
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/__snapshots__/class-validator.ts.snap
generated
vendored
Normal file
242
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/__snapshots__/class-validator.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`classValidatorResolver > should return a single error from classValidatorResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "birthYear must not be greater than 2013",
|
||||
"ref": undefined,
|
||||
"type": "max",
|
||||
},
|
||||
"email": {
|
||||
"message": "email must be an email",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "isEmail",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"name": {
|
||||
"message": "name must be longer than or equal to 4 characters",
|
||||
"ref": undefined,
|
||||
"type": "isLength",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "matches",
|
||||
},
|
||||
"username": {
|
||||
"message": "username must be longer than or equal to 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "isLength",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`classValidatorResolver > should return a single error from classValidatorResolver with \`mode: sync\` when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "birthYear must not be greater than 2013",
|
||||
"ref": undefined,
|
||||
"type": "max",
|
||||
},
|
||||
"email": {
|
||||
"message": "email must be an email",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "isEmail",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"name": {
|
||||
"message": "name must be longer than or equal to 4 characters",
|
||||
"ref": undefined,
|
||||
"type": "isLength",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "matches",
|
||||
},
|
||||
"username": {
|
||||
"message": "username must be longer than or equal to 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "isLength",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`classValidatorResolver > should return all the errors from classValidatorResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "birthYear must not be greater than 2013",
|
||||
"ref": undefined,
|
||||
"type": "max",
|
||||
"types": {
|
||||
"max": "birthYear must not be greater than 2013",
|
||||
"min": "birthYear must not be less than 1900",
|
||||
},
|
||||
},
|
||||
"email": {
|
||||
"message": "email must be an email",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "isEmail",
|
||||
"types": {
|
||||
"isEmail": "email must be an email",
|
||||
},
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"name": {
|
||||
"message": "name must be longer than or equal to 4 characters",
|
||||
"ref": undefined,
|
||||
"type": "isLength",
|
||||
"types": {
|
||||
"isLength": "name must be longer than or equal to 4 characters",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "matches",
|
||||
"types": {
|
||||
"matches": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "username must be longer than or equal to 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "isLength",
|
||||
"types": {
|
||||
"isLength": "username must be longer than or equal to 3 characters",
|
||||
"matches": "username must match /^\\w+$/ regular expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`classValidatorResolver > should return all the errors from classValidatorResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "birthYear must not be greater than 2013",
|
||||
"ref": undefined,
|
||||
"type": "max",
|
||||
"types": {
|
||||
"max": "birthYear must not be greater than 2013",
|
||||
"min": "birthYear must not be less than 1900",
|
||||
},
|
||||
},
|
||||
"email": {
|
||||
"message": "email must be an email",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "isEmail",
|
||||
"types": {
|
||||
"isEmail": "email must be an email",
|
||||
},
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"name": {
|
||||
"message": "name must be longer than or equal to 4 characters",
|
||||
"ref": undefined,
|
||||
"type": "isLength",
|
||||
"types": {
|
||||
"isLength": "name must be longer than or equal to 4 characters",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "matches",
|
||||
"types": {
|
||||
"matches": "password must match /^[a-zA-Z0-9]{3,30}/ regular expression",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": "username must be longer than or equal to 3 characters",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "isLength",
|
||||
"types": {
|
||||
"isLength": "username must be longer than or equal to 3 characters",
|
||||
"matches": "username must match /^\\w+$/ regular expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`validate data with transformer option 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"random": {
|
||||
"message": "All fields must be defined.",
|
||||
"ref": undefined,
|
||||
"type": "isDefined",
|
||||
"types": {
|
||||
"isDefined": "All fields must be defined.",
|
||||
"isNumber": "Must be a number",
|
||||
"max": "Cannot be greater than 255",
|
||||
"min": "Cannot be lower than 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`validate data with validator option 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"random": {
|
||||
"message": "All fields must be defined.",
|
||||
"ref": undefined,
|
||||
"type": "isDefined",
|
||||
"types": {
|
||||
"isDefined": "All fields must be defined.",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
188
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/class-validator.ts
generated
vendored
Normal file
188
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/__tests__/class-validator.ts
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
import { Expose, Type } from 'class-transformer';
|
||||
import * as classValidator from 'class-validator';
|
||||
import { IsDefined, IsNumber, Max, Min } from 'class-validator';
|
||||
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
|
||||
import { classValidatorResolver } from '..';
|
||||
import { Schema, fields, invalidData, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('classValidatorResolver', () => {
|
||||
it('should return values from classValidatorResolver when validation pass', async () => {
|
||||
const schemaSpy = vi.spyOn(classValidator, 'validate');
|
||||
const schemaSyncSpy = vi.spyOn(classValidator, 'validateSync');
|
||||
|
||||
const result = await classValidatorResolver(Schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(schemaSpy).toHaveBeenCalledTimes(1);
|
||||
expect(schemaSyncSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
expect(result.values).toBeInstanceOf(Schema);
|
||||
});
|
||||
|
||||
it('should return values as a raw object from classValidatorResolver when `rawValues` set to true', async () => {
|
||||
const result = await classValidatorResolver(Schema, undefined, {
|
||||
rawValues: true,
|
||||
})(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
expect(result.values).not.toBeInstanceOf(Schema);
|
||||
});
|
||||
|
||||
it('should return values from classValidatorResolver with `mode: sync` when validation pass', async () => {
|
||||
const validateSyncSpy = vi.spyOn(classValidator, 'validateSync');
|
||||
const validateSpy = vi.spyOn(classValidator, 'validate');
|
||||
|
||||
const result = await classValidatorResolver(Schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(validData, undefined, { fields, shouldUseNativeValidation });
|
||||
|
||||
expect(validateSyncSpy).toHaveBeenCalledTimes(1);
|
||||
expect(validateSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
expect(result.values).toBeInstanceOf(Schema);
|
||||
});
|
||||
|
||||
it('should return a single error from classValidatorResolver when validation fails', async () => {
|
||||
const result = await classValidatorResolver(Schema)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return a single error from classValidatorResolver with `mode: sync` when validation fails', async () => {
|
||||
const validateSyncSpy = vi.spyOn(classValidator, 'validateSync');
|
||||
const validateSpy = vi.spyOn(classValidator, 'validate');
|
||||
|
||||
const result = await classValidatorResolver(Schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(invalidData, undefined, { fields, shouldUseNativeValidation });
|
||||
|
||||
expect(validateSyncSpy).toHaveBeenCalledTimes(1);
|
||||
expect(validateSpy).not.toHaveBeenCalled();
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from classValidatorResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
|
||||
const result = await classValidatorResolver(Schema)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from classValidatorResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => {
|
||||
const result = await classValidatorResolver(Schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(invalidData, undefined, {
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('validate data with transformer option', async () => {
|
||||
class SchemaTest {
|
||||
@Expose({ groups: ['find', 'create', 'update'] })
|
||||
@Type(() => Number)
|
||||
@IsDefined({
|
||||
message: `All fields must be defined.`,
|
||||
groups: ['publish'],
|
||||
})
|
||||
@IsNumber({}, { message: `Must be a number`, always: true })
|
||||
@Min(0, { message: `Cannot be lower than 0`, always: true })
|
||||
@Max(255, { message: `Cannot be greater than 255`, always: true })
|
||||
random: number;
|
||||
}
|
||||
|
||||
const result = await classValidatorResolver(
|
||||
SchemaTest,
|
||||
{ transformer: { groups: ['update'] } },
|
||||
{
|
||||
mode: 'sync',
|
||||
},
|
||||
)(invalidData, undefined, {
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('validate data with validator option', async () => {
|
||||
class SchemaTest {
|
||||
@Expose({ groups: ['find', 'create', 'update'] })
|
||||
@Type(() => Number)
|
||||
@IsDefined({
|
||||
message: `All fields must be defined.`,
|
||||
groups: ['publish'],
|
||||
})
|
||||
@IsNumber({}, { message: `Must be a number`, always: true })
|
||||
@Min(0, { message: `Cannot be lower than 0`, always: true })
|
||||
@Max(255, { message: `Cannot be greater than 255`, always: true })
|
||||
random: number;
|
||||
}
|
||||
|
||||
const result = await classValidatorResolver(
|
||||
SchemaTest,
|
||||
{ validator: { stopAtFirstError: true } },
|
||||
{
|
||||
mode: 'sync',
|
||||
},
|
||||
)(invalidData, undefined, {
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return from classValidatorResolver with `excludeExtraneousValues` set to true', async () => {
|
||||
class SchemaTest {
|
||||
@Expose()
|
||||
@IsNumber({}, { message: `Must be a number`, always: true })
|
||||
random: number;
|
||||
}
|
||||
|
||||
const result = await classValidatorResolver(SchemaTest, {
|
||||
transformer: {
|
||||
excludeExtraneousValues: true,
|
||||
},
|
||||
})(
|
||||
{
|
||||
random: 10,
|
||||
extraneousField: true,
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: { random: 10 } });
|
||||
expect(result.values).toBeInstanceOf(SchemaTest);
|
||||
});
|
||||
67
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/class-validator.ts
generated
vendored
Normal file
67
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/class-validator.ts
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { ValidationError, validate, validateSync } from 'class-validator';
|
||||
import { FieldErrors } from 'react-hook-form';
|
||||
import type { Resolver } from './types';
|
||||
|
||||
const parseErrors = (
|
||||
errors: ValidationError[],
|
||||
validateAllFieldCriteria: boolean,
|
||||
parsedErrors: FieldErrors = {},
|
||||
path = '',
|
||||
) => {
|
||||
return errors.reduce((acc, error) => {
|
||||
const _path = path ? `${path}.${error.property}` : error.property;
|
||||
|
||||
if (error.constraints) {
|
||||
const key = Object.keys(error.constraints)[0];
|
||||
acc[_path] = {
|
||||
type: key,
|
||||
message: error.constraints[key],
|
||||
};
|
||||
|
||||
const _e = acc[_path];
|
||||
if (validateAllFieldCriteria && _e) {
|
||||
Object.assign(_e, { types: error.constraints });
|
||||
}
|
||||
}
|
||||
|
||||
if (error.children && error.children.length) {
|
||||
parseErrors(error.children, validateAllFieldCriteria, acc, _path);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, parsedErrors);
|
||||
};
|
||||
|
||||
export const classValidatorResolver: Resolver =
|
||||
(schema, schemaOptions = {}, resolverOptions = {}) =>
|
||||
async (values, _, options) => {
|
||||
const { transformer, validator } = schemaOptions;
|
||||
const data = plainToClass(schema, values, transformer);
|
||||
|
||||
const rawErrors = await (resolverOptions.mode === 'sync'
|
||||
? validateSync
|
||||
: validate)(data, validator);
|
||||
|
||||
if (rawErrors.length) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrors(
|
||||
rawErrors,
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
values: resolverOptions.rawValues ? values : data,
|
||||
errors: {},
|
||||
};
|
||||
};
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './class-validator';
|
||||
export * from './types';
|
||||
16
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/types.ts
generated
vendored
Normal file
16
install/config-ui/node_modules/@hookform/resolvers/class-validator/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ClassConstructor, ClassTransformOptions } from 'class-transformer';
|
||||
import { ValidatorOptions } from 'class-validator';
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
|
||||
export type Resolver = <T extends { [_: string]: any }>(
|
||||
schema: ClassConstructor<T>,
|
||||
schemaOptions?: {
|
||||
validator?: ValidatorOptions;
|
||||
transformer?: ClassTransformOptions;
|
||||
},
|
||||
resolverOptions?: { mode?: 'async' | 'sync'; rawValues?: boolean },
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => Promise<ResolverResult<TFieldValues>>;
|
||||
17
install/config-ui/node_modules/@hookform/resolvers/computed-types/package.json
generated
vendored
Normal file
17
install/config-ui/node_modules/@hookform/resolvers/computed-types/package.json
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/computed-types",
|
||||
"amdName": "hookformResolversComputedTypes",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: computed-types",
|
||||
"main": "dist/computed-types.js",
|
||||
"module": "dist/computed-types.module.js",
|
||||
"umd:main": "dist/computed-types.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"
|
||||
}
|
||||
}
|
||||
81
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
81
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import Schema, { Type, string } from 'computed-types';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { computedTypesResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
const schema = Schema({
|
||||
username: string.min(2).error(USERNAME_REQUIRED_MESSAGE),
|
||||
password: string.min(2).error(PASSWORD_REQUIRED_MESSAGE),
|
||||
});
|
||||
|
||||
type FormData = Type<typeof schema> & { unusedProperty: string };
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: computedTypesResolver(schema),
|
||||
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 computed-types", 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_REQUIRED_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('');
|
||||
});
|
||||
61
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/Form.tsx
generated
vendored
Normal file
61
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import Schema, { Type, string } from 'computed-types';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { computedTypesResolver } from '..';
|
||||
|
||||
const schema = Schema({
|
||||
username: string.min(2).error('username field is required'),
|
||||
password: string.min(2).error('password field is required'),
|
||||
address: Schema({
|
||||
zipCode: string.min(5).max(5).error('zipCode field is required'),
|
||||
}),
|
||||
});
|
||||
|
||||
type FormData = Type<typeof schema> & { unusedProperty: string };
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
resolver: computedTypesResolver(schema), // 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>}
|
||||
|
||||
<input {...register('address.zipCode')} />
|
||||
{errors.address?.zipCode && (
|
||||
<span role="alert">{errors.address.zipCode.message}</span>
|
||||
)}
|
||||
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
test("form's validation with computed-types 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 field is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password field is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/zipCode field is required/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
86
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
86
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import Schema, { Type, string, number, array, boolean } from 'computed-types';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = Schema({
|
||||
username: string.regexp(/^\w+$/).min(3).max(30),
|
||||
password: string
|
||||
.regexp(new RegExp('.*[A-Z].*'), 'One uppercase character')
|
||||
.regexp(new RegExp('.*[a-z].*'), 'One lowercase character')
|
||||
.regexp(new RegExp('.*\\d.*'), 'One number')
|
||||
.regexp(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
'One special character',
|
||||
)
|
||||
.min(8, 'Must be at least 8 characters in length'),
|
||||
repeatPassword: string,
|
||||
accessToken: Schema.either(string, number).optional(),
|
||||
birthYear: number.min(1900).max(2013).optional(),
|
||||
email: string
|
||||
.regexp(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)
|
||||
.error('Incorrect email'),
|
||||
tags: array.of(string),
|
||||
enabled: boolean,
|
||||
like: array
|
||||
.of({
|
||||
id: number,
|
||||
name: string.min(4).max(4),
|
||||
})
|
||||
.optional(),
|
||||
address: Schema({
|
||||
city: string.min(3, 'Is required'),
|
||||
zipCode: string
|
||||
.min(5, 'Must be 5 characters long')
|
||||
.max(5, 'Must be 5 characters long'),
|
||||
}),
|
||||
});
|
||||
|
||||
export const validData: Type<typeof schema> = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
accessToken: 'accessToken',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
address: {
|
||||
city: 'Awesome city',
|
||||
zipCode: '12345',
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z' }],
|
||||
address: {
|
||||
city: '',
|
||||
zipCode: '123',
|
||||
},
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
74
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/__snapshots__/computed-types.ts.snap
generated
vendored
Normal file
74
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/__snapshots__/computed-types.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`computedTypesResolver > should return a single error from computedTypesResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"address": {
|
||||
"city": {
|
||||
"message": "Is required",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"zipCode": {
|
||||
"message": "Must be 5 characters long",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
},
|
||||
"birthYear": {
|
||||
"message": "Expect value to be "number"",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"email": {
|
||||
"message": "Incorrect email",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"enabled": {
|
||||
"message": "Expect value to be "boolean"",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"like": {
|
||||
"id": {
|
||||
"message": "Expect value to be "number"",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"name": {
|
||||
"message": "Expect value to be "string"",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Expect value to be "string"",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"tags": {
|
||||
"message": "Expecting value to be an array",
|
||||
"ref": undefined,
|
||||
"type": "ValidationError",
|
||||
},
|
||||
"username": {
|
||||
"message": "Expect value to be "string"",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "ValidationError",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
40
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/computed-types.ts
generated
vendored
Normal file
40
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/__tests__/computed-types.ts
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { computedTypesResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('computedTypesResolver', () => {
|
||||
it('should return values from computedTypesResolver when validation pass', async () => {
|
||||
const result = await computedTypesResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from computedTypesResolver when validation fails', async () => {
|
||||
const result = await computedTypesResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw any error unrelated to computed-types', async () => {
|
||||
const schemaWithCustomError = schema.transform(() => {
|
||||
throw Error('custom error');
|
||||
});
|
||||
const promise = computedTypesResolver(schemaWithCustomError)(
|
||||
validData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
await expect(promise).rejects.toThrow('custom error');
|
||||
});
|
||||
});
|
||||
42
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/computed-types.ts
generated
vendored
Normal file
42
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/computed-types.ts
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import type { ValidationError } from 'computed-types';
|
||||
import type { FieldErrors } from 'react-hook-form';
|
||||
import type { Resolver } from './types';
|
||||
|
||||
const isValidationError = (error: any): error is ValidationError =>
|
||||
error.errors != null;
|
||||
|
||||
const parseErrorSchema = (computedTypesError: ValidationError) => {
|
||||
const parsedErrors: FieldErrors = {};
|
||||
return (computedTypesError.errors || []).reduce((acc, error) => {
|
||||
acc[error.path.join('.')] = {
|
||||
type: error.error.name,
|
||||
message: error.error.message,
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, parsedErrors);
|
||||
};
|
||||
|
||||
export const computedTypesResolver: Resolver =
|
||||
(schema) => async (values, _, options) => {
|
||||
try {
|
||||
const data = await schema(values);
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
errors: {},
|
||||
values: data,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (isValidationError(error)) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(parseErrorSchema(error), options),
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './computed-types';
|
||||
export * from './types';
|
||||
13
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/types.ts
generated
vendored
Normal file
13
install/config-ui/node_modules/@hookform/resolvers/computed-types/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import type {
|
||||
FieldValues,
|
||||
ResolverOptions,
|
||||
ResolverResult,
|
||||
} from 'react-hook-form';
|
||||
|
||||
export type Resolver = (
|
||||
schema: any,
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => Promise<ResolverResult<TFieldValues>>;
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/effect-ts/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/effect-ts/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/effect-ts",
|
||||
"amdName": "hookformResolversEffectTs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: effect-ts",
|
||||
"main": "dist/effect-ts.js",
|
||||
"module": "dist/effect-ts.module.js",
|
||||
"umd:main": "dist/effect-ts.umd.js",
|
||||
"source": "src/index.ts",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@hookform/resolvers": "^2.0.0",
|
||||
"effect": "^3.10.3",
|
||||
"react-hook-form": "^7.0.0"
|
||||
}
|
||||
}
|
||||
88
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
88
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { Schema } from 'effect';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { effectTsResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
const schema = Schema.Struct({
|
||||
username: Schema.String.pipe(
|
||||
Schema.nonEmptyString({ message: () => USERNAME_REQUIRED_MESSAGE }),
|
||||
),
|
||||
password: Schema.String.pipe(
|
||||
Schema.nonEmptyString({ message: () => PASSWORD_REQUIRED_MESSAGE }),
|
||||
),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: effectTsResolver(schema),
|
||||
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 effect-ts", 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_REQUIRED_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('');
|
||||
});
|
||||
59
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
59
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { Schema } from 'effect';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { effectTsResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
const schema = Schema.Struct({
|
||||
username: Schema.String.pipe(
|
||||
Schema.nonEmptyString({ message: () => USERNAME_REQUIRED_MESSAGE }),
|
||||
),
|
||||
password: Schema.String.pipe(
|
||||
Schema.nonEmptyString({ message: () => PASSWORD_REQUIRED_MESSAGE }),
|
||||
),
|
||||
});
|
||||
|
||||
type FormData = Schema.Schema.Type<typeof schema>;
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: effectTsResolver(schema),
|
||||
});
|
||||
|
||||
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 Zod 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 field is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password field is required/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
124
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
124
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Schema } from 'effect';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = Schema.Struct({
|
||||
username: Schema.String.pipe(
|
||||
Schema.nonEmptyString({ message: () => 'A username is required' }),
|
||||
),
|
||||
password: Schema.String.pipe(
|
||||
Schema.pattern(new RegExp('.*[A-Z].*'), {
|
||||
message: () => 'At least 1 uppercase letter.',
|
||||
}),
|
||||
Schema.pattern(new RegExp('.*[a-z].*'), {
|
||||
message: () => 'At least 1 lowercase letter.',
|
||||
}),
|
||||
Schema.pattern(new RegExp('.*\\d.*'), {
|
||||
message: () => 'At least 1 number.',
|
||||
}),
|
||||
Schema.pattern(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
{
|
||||
message: () => 'At least 1 special character.',
|
||||
},
|
||||
),
|
||||
Schema.minLength(8, { message: () => 'Must be at least 8 characters.' }),
|
||||
),
|
||||
accessToken: Schema.Union(Schema.String, Schema.Number),
|
||||
birthYear: Schema.Number.pipe(
|
||||
Schema.greaterThan(1900, {
|
||||
message: () => 'Must be greater than the year 1900',
|
||||
}),
|
||||
Schema.filter((value) => value < new Date().getFullYear(), {
|
||||
message: () => 'Must be before the current year.',
|
||||
}),
|
||||
),
|
||||
email: Schema.String.pipe(
|
||||
Schema.pattern(
|
||||
new RegExp(
|
||||
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i,
|
||||
),
|
||||
{
|
||||
message: () => 'A valid email address is required.',
|
||||
},
|
||||
),
|
||||
),
|
||||
tags: Schema.Array(
|
||||
Schema.Struct({
|
||||
name: Schema.String,
|
||||
}),
|
||||
),
|
||||
luckyNumbers: Schema.Array(Schema.Number),
|
||||
enabled: Schema.Boolean,
|
||||
animal: Schema.Union(Schema.String, Schema.Literal('bird', 'snake')),
|
||||
vehicles: Schema.Array(
|
||||
Schema.Union(
|
||||
Schema.Struct({
|
||||
type: Schema.Literal('car'),
|
||||
brand: Schema.String,
|
||||
horsepower: Schema.Number,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal('bike'),
|
||||
speed: Schema.Number,
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
export const validData: Schema.Schema.Type<typeof schema> = {
|
||||
accessToken: 'abcd1234',
|
||||
animal: 'dog',
|
||||
birthYear: 2000,
|
||||
email: 'johnDoe@here.there',
|
||||
enabled: true,
|
||||
luckyNumbers: [1, 2, 3, 4, 5],
|
||||
password: 'Super#Secret123',
|
||||
tags: [{ name: 'move' }, { name: 'over' }, { name: 'zod' }, { name: ';)' }],
|
||||
username: 'johnDoe',
|
||||
vehicles: [
|
||||
{ type: 'bike', speed: 5 },
|
||||
{ type: 'car', brand: 'BMW', horsepower: 150 },
|
||||
],
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
username: 'test',
|
||||
password: 'Password123',
|
||||
repeatPassword: 'Password123',
|
||||
birthYear: 2000,
|
||||
accessToken: '1015d809-e99d-41ec-b161-981a3c243df8',
|
||||
email: 'john@doe.com',
|
||||
tags: [{ name: 'test' }],
|
||||
enabled: true,
|
||||
animal: ['dog'],
|
||||
luckyNumbers: [1, 2, '3'],
|
||||
like: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
vehicles: [
|
||||
{ type: 'car', brand: 'BMW', horsepower: 150 },
|
||||
{ type: 'car', brand: 'Mercedes' },
|
||||
],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
40
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap
generated
vendored
Normal file
40
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`effectTsResolver > should return a single error from effectTsResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"animal": {
|
||||
"message": "Expected "snake", actual ["dog"]",
|
||||
"ref": undefined,
|
||||
"type": "Type",
|
||||
},
|
||||
"luckyNumbers": [
|
||||
,
|
||||
,
|
||||
{
|
||||
"message": "Expected number, actual "3"",
|
||||
"ref": undefined,
|
||||
"type": "Type",
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "At least 1 special character.",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "Refinement",
|
||||
},
|
||||
"vehicles": [
|
||||
,
|
||||
{
|
||||
"horsepower": {
|
||||
"message": "is missing",
|
||||
"ref": undefined,
|
||||
"type": "Missing",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
24
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/effect-ts.ts
generated
vendored
Normal file
24
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/__tests__/effect-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { effectTsResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('effectTsResolver', () => {
|
||||
it('should return values from effectTsResolver when validation pass', async () => {
|
||||
const result = await effectTsResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from effectTsResolver when validation fails', async () => {
|
||||
const result = await effectTsResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
40
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/effect-ts.ts
generated
vendored
Normal file
40
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/effect-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import { Effect } from 'effect';
|
||||
|
||||
import { ArrayFormatter, decodeUnknown } from 'effect/ParseResult';
|
||||
import type { FieldErrors } from 'react-hook-form';
|
||||
import type { Resolver } from './types';
|
||||
|
||||
export const effectTsResolver: Resolver =
|
||||
(schema, config = { errors: 'all', onExcessProperty: 'ignore' }) =>
|
||||
(values, _, options) => {
|
||||
return decodeUnknown(
|
||||
schema,
|
||||
config,
|
||||
)(values).pipe(
|
||||
Effect.catchAll((parseIssue) =>
|
||||
Effect.flip(ArrayFormatter.formatIssue(parseIssue)),
|
||||
),
|
||||
Effect.mapError((issues) => {
|
||||
const errors = issues.reduce((acc, current) => {
|
||||
const key = current.path.join('.');
|
||||
acc[key] = { message: current.message, type: current._tag };
|
||||
return acc;
|
||||
}, {} as FieldErrors);
|
||||
|
||||
return toNestErrors(errors, options);
|
||||
}),
|
||||
Effect.tap(() =>
|
||||
Effect.sync(
|
||||
() =>
|
||||
options.shouldUseNativeValidation &&
|
||||
validateFieldsNatively({}, options),
|
||||
),
|
||||
),
|
||||
Effect.match({
|
||||
onFailure: (errors) => ({ errors, values: {} }),
|
||||
onSuccess: (result) => ({ errors: {}, values: result }),
|
||||
}),
|
||||
Effect.runPromise,
|
||||
);
|
||||
};
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './effect-ts';
|
||||
export * from './types';
|
||||
12
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/types.ts
generated
vendored
Normal file
12
install/config-ui/node_modules/@hookform/resolvers/effect-ts/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Schema } from 'effect';
|
||||
import { ParseOptions } from 'effect/SchemaAST';
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
|
||||
export type Resolver = <A extends FieldValues, I, TContext>(
|
||||
schema: Schema.Schema<A, I>,
|
||||
config?: ParseOptions,
|
||||
) => (
|
||||
values: FieldValues,
|
||||
_context: TContext | undefined,
|
||||
options: ResolverOptions<A>,
|
||||
) => Promise<ResolverResult<A>>;
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/fluentvalidation-ts",
|
||||
"amdName": "hookformResolversfluentvalidation-ts",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: fluentvalidation-ts",
|
||||
"main": "dist/fluentvalidation-ts.js",
|
||||
"module": "dist/fluentvalidation-ts.module.js",
|
||||
"umd:main": "dist/fluentvalidation-ts.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",
|
||||
"fluentvalidation-ts": "^3.0.0"
|
||||
}
|
||||
}
|
||||
88
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
88
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { Validator } from 'fluentvalidation-ts';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { fluentValidationResolver } from '../fluentvalidation-ts';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
type FormData = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
class FormDataValidator extends Validator<FormData> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.ruleFor('username').notEmpty().withMessage(USERNAME_REQUIRED_MESSAGE);
|
||||
this.ruleFor('password').notEmpty().withMessage(PASSWORD_REQUIRED_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: fluentValidationResolver(new FormDataValidator()),
|
||||
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 fluentvalidation-ts", 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_REQUIRED_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('');
|
||||
});
|
||||
63
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
63
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import { Validator } from 'fluentvalidation-ts';
|
||||
import React from 'react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { fluentValidationResolver } from '../fluentvalidation-ts';
|
||||
|
||||
type FormData = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
class FormDataValidator extends Validator<FormData> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.ruleFor('username')
|
||||
.notEmpty()
|
||||
.withMessage('username is a required field');
|
||||
this.ruleFor('password')
|
||||
.notEmpty()
|
||||
.withMessage('password is a required field');
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: SubmitHandler<FormData>;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm({
|
||||
resolver: fluentValidationResolver(new FormDataValidator()), // 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 Yup 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 a required field/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password is a required field/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
121
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
121
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Validator } from 'fluentvalidation-ts';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
const beNumeric = (value: string | number | undefined) => !isNaN(Number(value));
|
||||
|
||||
export type Schema = {
|
||||
username: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
accessToken?: string;
|
||||
birthYear?: number;
|
||||
email?: string;
|
||||
tags?: string[];
|
||||
enabled?: boolean;
|
||||
like?: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type SchemaWithWhen = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export class SchemaValidator extends Validator<Schema> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.ruleFor('username')
|
||||
.notEmpty()
|
||||
.matches(/^\w+$/)
|
||||
.minLength(3)
|
||||
.maxLength(30);
|
||||
|
||||
this.ruleFor('password')
|
||||
.notEmpty()
|
||||
.matches(/.*[A-Z].*/)
|
||||
.withMessage('One uppercase character')
|
||||
.matches(/.*[a-z].*/)
|
||||
.withMessage('One lowercase character')
|
||||
.matches(/.*\d.*/)
|
||||
.withMessage('One number')
|
||||
.matches(new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'))
|
||||
.withMessage('One special character')
|
||||
.minLength(8)
|
||||
.withMessage('Must be at least 8 characters in length');
|
||||
|
||||
this.ruleFor('repeatPassword')
|
||||
.notEmpty()
|
||||
.must((repeatPassword, data) => repeatPassword === data.password);
|
||||
|
||||
this.ruleFor('accessToken');
|
||||
this.ruleFor('birthYear')
|
||||
.must(beNumeric)
|
||||
.inclusiveBetween(1900, 2013)
|
||||
.when((birthYear) => birthYear != undefined);
|
||||
|
||||
this.ruleFor('email').emailAddress();
|
||||
this.ruleFor('tags');
|
||||
this.ruleFor('enabled');
|
||||
|
||||
this.ruleForEach('like').setValidator(() => new LikeValidator());
|
||||
}
|
||||
}
|
||||
|
||||
export class LikeValidator extends Validator<{
|
||||
id: number;
|
||||
name: string;
|
||||
}> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.ruleFor('id').notNull();
|
||||
this.ruleFor('name').notEmpty().length(4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
export const validData = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
accesstoken: 'accesstoken',
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
} as Schema;
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z' }],
|
||||
// Must be set to "unknown", otherwise typescript knows that it is invalid
|
||||
} as unknown as Required<Schema>;
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
129
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/__snapshots__/fluentvalidation-ts.ts.snap
generated
vendored
Normal file
129
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/__snapshots__/fluentvalidation-ts.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`fluentValidationResolver > should return a single error from fluentValidationResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
"email": {
|
||||
"message": "Not a valid email address",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`fluentValidationResolver > should return a single error from fluentValidationResolver with \`mode: sync\` when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
"email": {
|
||||
"message": "Not a valid email address",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`fluentValidationResolver > should return all the errors from fluentValidationResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
"email": {
|
||||
"message": "Not a valid email address",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`fluentValidationResolver > should return all the errors from fluentValidationResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
"email": {
|
||||
"message": "Not a valid email address",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "validation",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Value is not valid",
|
||||
"ref": undefined,
|
||||
"type": "validation",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
113
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/fluentvalidation-ts.ts
generated
vendored
Normal file
113
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/__tests__/fluentvalidation-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
|
||||
import { fluentValidationResolver } from '..';
|
||||
import {
|
||||
SchemaValidator,
|
||||
fields,
|
||||
invalidData,
|
||||
validData,
|
||||
} from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
const validator = new SchemaValidator();
|
||||
|
||||
describe('fluentValidationResolver', () => {
|
||||
it('should return values from fluentValidationResolver when validation pass', async () => {
|
||||
const validatorSpy = vi.spyOn(validator, 'validate');
|
||||
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
validData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(validatorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return values from fluentValidationResolver with `mode: sync` when validation pass', async () => {
|
||||
const validatorSpy = vi.spyOn(validator, 'validate');
|
||||
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
validData,
|
||||
undefined,
|
||||
{ fields, shouldUseNativeValidation },
|
||||
);
|
||||
|
||||
expect(validatorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from fluentValidationResolver when validation fails', async () => {
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return a single error from fluentValidationResolver with `mode: sync` when validation fails', async () => {
|
||||
const validateSpy = vi.spyOn(validator, 'validate');
|
||||
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{ fields, shouldUseNativeValidation },
|
||||
);
|
||||
|
||||
expect(validateSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from fluentValidationResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from fluentValidationResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => {
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return values from fluentValidationResolver when validation pass & raw=true', async () => {
|
||||
const schemaSpy = vi.spyOn(validator, 'validate');
|
||||
|
||||
const result = await fluentValidationResolver(validator)(
|
||||
validData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(schemaSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
});
|
||||
102
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/fluentvalidation-ts.ts
generated
vendored
Normal file
102
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/fluentvalidation-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import {
|
||||
AsyncValidator,
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
} from 'fluentvalidation-ts';
|
||||
import { FieldError, FieldValues, Resolver } from 'react-hook-form';
|
||||
|
||||
function traverseObject<T>(
|
||||
object: ValidationErrors<T>,
|
||||
errors: Record<string, FieldError>,
|
||||
parentIndices: (string | number)[] = [],
|
||||
) {
|
||||
for (const key in object) {
|
||||
const currentIndex = [...parentIndices, key];
|
||||
const currentValue = object[key];
|
||||
|
||||
if (Array.isArray(currentValue)) {
|
||||
currentValue.forEach((item: any, index: number) => {
|
||||
traverseObject(item, errors, [...currentIndex, index]);
|
||||
});
|
||||
} else if (typeof currentValue === 'object' && currentValue !== null) {
|
||||
traverseObject(currentValue, errors, currentIndex);
|
||||
} else if (typeof currentValue === 'string') {
|
||||
errors[currentIndex.join('.')] = {
|
||||
type: 'validation',
|
||||
message: currentValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parseErrorSchema = <T>(
|
||||
validationErrors: ValidationErrors<T>,
|
||||
validateAllFieldCriteria: boolean,
|
||||
) => {
|
||||
if (validateAllFieldCriteria) {
|
||||
// TODO: check this but i think its always one validation error
|
||||
}
|
||||
|
||||
const errors: Record<string, FieldError> = {};
|
||||
traverseObject(validationErrors, errors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
export function fluentValidationResolver<TFieldValues extends FieldValues>(
|
||||
validator: Validator<TFieldValues>,
|
||||
): Resolver<TFieldValues> {
|
||||
return async (values, _context, options) => {
|
||||
const validationResult = validator.validate(values);
|
||||
const isValid = Object.keys(validationResult).length === 0;
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return isValid
|
||||
? {
|
||||
values: values,
|
||||
errors: {},
|
||||
}
|
||||
: {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrorSchema(
|
||||
validationResult,
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function fluentAsyncValidationResolver<
|
||||
TFieldValues extends FieldValues,
|
||||
TValidator extends AsyncValidator<TFieldValues>,
|
||||
>(validator: TValidator): Resolver<TFieldValues> {
|
||||
return async (values, _context, options) => {
|
||||
const validationResult = await validator.validateAsync(values);
|
||||
const isValid = Object.keys(validationResult).length === 0;
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return isValid
|
||||
? {
|
||||
values: values,
|
||||
errors: {},
|
||||
}
|
||||
: {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrorSchema(
|
||||
validationResult,
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
};
|
||||
}
|
||||
1
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/index.ts
generated
vendored
Normal file
1
install/config-ui/node_modules/@hookform/resolvers/fluentvalidation-ts/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './fluentvalidation-ts';
|
||||
19
install/config-ui/node_modules/@hookform/resolvers/io-ts/package.json
generated
vendored
Normal file
19
install/config-ui/node_modules/@hookform/resolvers/io-ts/package.json
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/io-ts",
|
||||
"amdName": "hookformResolversIoTs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: io-ts",
|
||||
"main": "dist/io-ts.js",
|
||||
"module": "dist/io-ts.module.js",
|
||||
"umd:main": "dist/io-ts.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",
|
||||
"io-ts": "^2.0.0",
|
||||
"fp-ts": "^2.7.0"
|
||||
}
|
||||
}
|
||||
85
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
85
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import * as t from 'io-ts';
|
||||
import * as tt from 'io-ts-types';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ioTsResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
const schema = t.type({
|
||||
username: tt.withMessage(tt.NonEmptyString, () => USERNAME_REQUIRED_MESSAGE),
|
||||
password: tt.withMessage(tt.NonEmptyString, () => PASSWORD_REQUIRED_MESSAGE),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: ioTsResolver(schema),
|
||||
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 io-ts", 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_REQUIRED_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('');
|
||||
});
|
||||
63
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
63
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import * as t from 'io-ts';
|
||||
import * as tt from 'io-ts-types';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ioTsResolver } from '..';
|
||||
|
||||
const schema = t.type({
|
||||
username: tt.withMessage(
|
||||
tt.NonEmptyString,
|
||||
() => 'username is a required field',
|
||||
),
|
||||
password: tt.withMessage(
|
||||
tt.NonEmptyString,
|
||||
() => 'password is a required field',
|
||||
),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: ioTsResolver(schema),
|
||||
criteriaMode: 'all',
|
||||
});
|
||||
|
||||
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 io-ts 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 a required field/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/password is a required field/i)).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
129
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
129
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import * as t from 'io-ts';
|
||||
import * as tt from 'io-ts-types';
|
||||
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = t.intersection([
|
||||
t.type({
|
||||
username: tt.NonEmptyString,
|
||||
password: tt.NonEmptyString,
|
||||
accessToken: tt.UUID,
|
||||
birthYear: t.number,
|
||||
email: t.string,
|
||||
tags: t.array(
|
||||
t.type({
|
||||
name: t.string,
|
||||
}),
|
||||
),
|
||||
luckyNumbers: t.array(t.number),
|
||||
enabled: t.boolean,
|
||||
animal: t.union([
|
||||
t.string,
|
||||
t.number,
|
||||
t.literal('bird'),
|
||||
t.literal('snake'),
|
||||
]),
|
||||
vehicles: t.array(
|
||||
t.union([
|
||||
t.type({
|
||||
type: t.literal('car'),
|
||||
brand: t.string,
|
||||
horsepower: t.number,
|
||||
}),
|
||||
t.type({
|
||||
type: t.literal('bike'),
|
||||
speed: t.number,
|
||||
}),
|
||||
]),
|
||||
),
|
||||
}),
|
||||
t.partial({
|
||||
like: t.array(
|
||||
t.type({
|
||||
id: tt.withMessage(
|
||||
t.number,
|
||||
(i) => `this id is very important but you passed: ${typeof i}(${i})`,
|
||||
),
|
||||
name: t.string,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
interface Data {
|
||||
username: string;
|
||||
password: string;
|
||||
accessToken: string;
|
||||
birthYear?: number;
|
||||
luckyNumbers: number[];
|
||||
email?: string;
|
||||
animal: string | number;
|
||||
tags: { name: string }[];
|
||||
enabled: boolean;
|
||||
like: { id: number; name: string }[];
|
||||
vehicles: Array<
|
||||
| { type: 'car'; brand: string; horsepower: number }
|
||||
| { type: 'bike'; speed: number }
|
||||
>;
|
||||
}
|
||||
|
||||
export const validData: Data = {
|
||||
username: 'Doe',
|
||||
password: 'Password123',
|
||||
accessToken: 'c2883927-5178-4ad1-bbee-07ba33a5de19',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: [{ name: 'test' }],
|
||||
enabled: true,
|
||||
luckyNumbers: [17, 5],
|
||||
animal: 'cat',
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
vehicles: [{ type: 'car', brand: 'BMW', horsepower: 150 }],
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
username: 'test',
|
||||
password: 'Password123',
|
||||
repeatPassword: 'Password123',
|
||||
birthYear: 2000,
|
||||
accessToken: '1015d809-e99d-41ec-b161-981a3c243df8',
|
||||
email: 'john@doe.com',
|
||||
tags: [{ name: 'test' }],
|
||||
enabled: true,
|
||||
animal: ['dog'],
|
||||
luckyNumbers: [1, 2, '3'],
|
||||
like: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
vehicles: [
|
||||
{ type: 'car', brand: 'BMW', horsepower: 150 },
|
||||
{ type: 'car', brand: 'Mercedes' },
|
||||
],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
89
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/__snapshots__/io-ts.ts.snap
generated
vendored
Normal file
89
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/__snapshots__/io-ts.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ioTsResolver > should return a single error from ioTsResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"animal": {
|
||||
"message": "expected string but got ["dog"]",
|
||||
"ref": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": "this id is very important but you passed: string(1)",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
],
|
||||
"luckyNumbers": [
|
||||
,
|
||||
,
|
||||
{
|
||||
"message": "expected number but got "3"",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"vehicles": [
|
||||
,
|
||||
{
|
||||
"horsepower": {
|
||||
"message": "expected number but got undefined",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ioTsResolver > should return all the errors from ioTsResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"animal": {
|
||||
"message": "expected "snake" but got ["dog"]",
|
||||
"ref": undefined,
|
||||
"type": ""snake"",
|
||||
"types": {
|
||||
""bird"": "expected "bird" but got ["dog"]",
|
||||
""snake"": "expected "snake" but got ["dog"]",
|
||||
"number": "expected number but got ["dog"]",
|
||||
"string": "expected string but got ["dog"]",
|
||||
},
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": "this id is very important but you passed: string(1)",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
],
|
||||
"luckyNumbers": [
|
||||
,
|
||||
,
|
||||
{
|
||||
"message": "expected number but got "3"",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"vehicles": [
|
||||
,
|
||||
{
|
||||
"horsepower": {
|
||||
"message": "expected number but got undefined",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
57
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/errorsToRecord.ts
generated
vendored
Normal file
57
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/errorsToRecord.ts
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import { isLeft } from 'fp-ts/Either';
|
||||
import * as t from 'io-ts';
|
||||
import errorsToRecord from '../errorsToRecord';
|
||||
|
||||
const assertLeft = <T>(result: t.Validation<T>) => {
|
||||
expect(isLeft(result)).toBe(true);
|
||||
if (!isLeft(result)) {
|
||||
throw new Error(
|
||||
'panic! error is not of the "left" type, should be unreachable',
|
||||
);
|
||||
}
|
||||
return result.left;
|
||||
};
|
||||
|
||||
const FIRST_NAME_FIELD_PATH = 'firstName' as const;
|
||||
const FIRST_NAME_ERROR_SHAPE = {
|
||||
message: 'expected string but got undefined',
|
||||
type: 'string',
|
||||
};
|
||||
describe('errorsToRecord', () => {
|
||||
it('should return a correct error for an exact intersection type error object', () => {
|
||||
// a recommended pattern from https://github.com/gcanti/io-ts/blob/master/index.md#mixing-required-and-optional-props
|
||||
const schema = t.exact(
|
||||
t.intersection([
|
||||
t.type({
|
||||
[FIRST_NAME_FIELD_PATH]: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
lastName: t.string,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const error = assertLeft(schema.decode({}));
|
||||
const record = errorsToRecord(false)(error);
|
||||
expect(record[FIRST_NAME_FIELD_PATH]).toMatchObject(FIRST_NAME_ERROR_SHAPE);
|
||||
});
|
||||
it('should return a correct error for a branded intersection', () => {
|
||||
interface Brand {
|
||||
readonly Brand: unique symbol;
|
||||
}
|
||||
const schema = t.brand(
|
||||
t.intersection([
|
||||
t.type({
|
||||
[FIRST_NAME_FIELD_PATH]: t.string,
|
||||
}),
|
||||
t.type({
|
||||
lastName: t.string,
|
||||
}),
|
||||
]),
|
||||
(_x): _x is t.Branded<typeof _x, Brand> => true,
|
||||
'Brand',
|
||||
);
|
||||
const error = assertLeft(schema.decode({}));
|
||||
const record = errorsToRecord(false)(error);
|
||||
expect(record[FIRST_NAME_FIELD_PATH]).toMatchObject(FIRST_NAME_ERROR_SHAPE);
|
||||
});
|
||||
});
|
||||
37
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/io-ts.ts
generated
vendored
Normal file
37
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/__tests__/io-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ioTsResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('ioTsResolver', () => {
|
||||
it('should return values from ioTsResolver when validation pass', async () => {
|
||||
const validateSpy = vi.spyOn(schema, 'decode');
|
||||
|
||||
const result = ioTsResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(validateSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from ioTsResolver when validation fails', () => {
|
||||
const result = ioTsResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from ioTsResolver when validation fails with `validateAllFieldCriteria` set to true', () => {
|
||||
const result = ioTsResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/arrayToPath.ts
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/arrayToPath.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Either from 'fp-ts/Either';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
|
||||
const arrayToPath = (paths: Either.Either<string, number>[]): string =>
|
||||
paths.reduce(
|
||||
(previous, path, index) =>
|
||||
pipe(
|
||||
path,
|
||||
Either.fold(
|
||||
(key) => `${index > 0 ? '.' : ''}${key}`,
|
||||
(key) => `[${key}]`,
|
||||
),
|
||||
(path) => `${previous}${path}`,
|
||||
),
|
||||
'',
|
||||
);
|
||||
|
||||
export default arrayToPath;
|
||||
141
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/errorsToRecord.ts
generated
vendored
Normal file
141
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/errorsToRecord.ts
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
import * as Either from 'fp-ts/Either';
|
||||
import * as Option from 'fp-ts/Option';
|
||||
import * as ReadonlyArray from 'fp-ts/ReadonlyArray';
|
||||
import * as ReadonlyRecord from 'fp-ts/ReadonlyRecord';
|
||||
import * as SemiGroup from 'fp-ts/Semigroup';
|
||||
import { absurd, flow, identity, not, pipe } from 'fp-ts/function';
|
||||
import * as t from 'io-ts';
|
||||
import {
|
||||
ExactType,
|
||||
IntersectionType,
|
||||
RefinementType,
|
||||
TaggedUnionType,
|
||||
UnionType,
|
||||
ValidationError,
|
||||
} from 'io-ts';
|
||||
import arrayToPath from './arrayToPath';
|
||||
import { ErrorObject, FieldErrorWithPath } from './types';
|
||||
|
||||
const INSTANCE_TYPES_TO_FILTER = [
|
||||
TaggedUnionType,
|
||||
UnionType,
|
||||
IntersectionType,
|
||||
ExactType,
|
||||
RefinementType,
|
||||
];
|
||||
const formatErrorPath = (context: t.Context): string =>
|
||||
pipe(
|
||||
context,
|
||||
ReadonlyArray.filterMapWithIndex((index, contextEntry) => {
|
||||
const previousIndex = index - 1;
|
||||
const previousContextEntry =
|
||||
previousIndex === -1 ? undefined : context[previousIndex];
|
||||
const shouldBeFiltered =
|
||||
previousContextEntry === undefined ||
|
||||
INSTANCE_TYPES_TO_FILTER.some(
|
||||
(type) => previousContextEntry.type instanceof type,
|
||||
);
|
||||
|
||||
return shouldBeFiltered ? Option.none : Option.some(contextEntry);
|
||||
}),
|
||||
ReadonlyArray.map(({ key }) => key),
|
||||
ReadonlyArray.map((key) =>
|
||||
pipe(
|
||||
key,
|
||||
(k) => parseInt(k, 10),
|
||||
Either.fromPredicate(not<number>(Number.isNaN), () => key),
|
||||
),
|
||||
),
|
||||
ReadonlyArray.toArray,
|
||||
arrayToPath,
|
||||
);
|
||||
|
||||
const formatError = (e: t.ValidationError): FieldErrorWithPath => {
|
||||
const path = formatErrorPath(e.context);
|
||||
|
||||
const message = pipe(
|
||||
e.message,
|
||||
Either.fromNullable(e.context),
|
||||
Either.mapLeft(
|
||||
flow(
|
||||
ReadonlyArray.last,
|
||||
Option.map(
|
||||
(contextEntry) =>
|
||||
`expected ${contextEntry.type.name} but got ${JSON.stringify(
|
||||
contextEntry.actual,
|
||||
)}`,
|
||||
),
|
||||
Option.getOrElseW(() =>
|
||||
absurd<string>('Error context is missing name' as never),
|
||||
),
|
||||
),
|
||||
),
|
||||
Either.getOrElseW(identity),
|
||||
);
|
||||
|
||||
const type = pipe(
|
||||
e.context,
|
||||
ReadonlyArray.last,
|
||||
Option.map((contextEntry) => contextEntry.type.name),
|
||||
Option.getOrElse(() => 'unknown'),
|
||||
);
|
||||
|
||||
return { message, type, path };
|
||||
};
|
||||
|
||||
// this is almost the same function like Semigroup.getObjectSemigroup but reversed
|
||||
// in order to get the first error
|
||||
const getObjectSemigroup = <
|
||||
A extends Record<string, unknown> = never,
|
||||
>(): SemiGroup.Semigroup<A> => ({
|
||||
concat: (first, second) => Object.assign({}, second, first),
|
||||
});
|
||||
|
||||
const concatToSingleError = (
|
||||
errors: ReadonlyArray<FieldErrorWithPath>,
|
||||
): ErrorObject =>
|
||||
pipe(
|
||||
errors,
|
||||
ReadonlyArray.map((error) => ({
|
||||
[error.path]: {
|
||||
type: error.type,
|
||||
message: error.message,
|
||||
},
|
||||
})),
|
||||
(errors) => SemiGroup.fold(getObjectSemigroup<ErrorObject>())({}, errors),
|
||||
);
|
||||
|
||||
const appendSeveralErrors: SemiGroup.Semigroup<FieldErrorWithPath> = {
|
||||
concat: (a, b) => ({
|
||||
...b,
|
||||
types: { ...a.types, [a.type]: a.message, [b.type]: b.message },
|
||||
}),
|
||||
};
|
||||
|
||||
const concatToMultipleErrors = (
|
||||
errors: ReadonlyArray<FieldErrorWithPath>,
|
||||
): ErrorObject =>
|
||||
pipe(
|
||||
ReadonlyRecord.fromFoldableMap(appendSeveralErrors, ReadonlyArray.Foldable)(
|
||||
errors,
|
||||
(error) => [error.path, error],
|
||||
),
|
||||
ReadonlyRecord.map((errorWithPath) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { path, ...error } = errorWithPath;
|
||||
|
||||
return error;
|
||||
}),
|
||||
);
|
||||
|
||||
const errorsToRecord =
|
||||
(validateAllFieldCriteria: boolean) =>
|
||||
(validationErrors: ReadonlyArray<ValidationError>): ErrorObject => {
|
||||
const concat = validateAllFieldCriteria
|
||||
? concatToMultipleErrors
|
||||
: concatToSingleError;
|
||||
|
||||
return pipe(validationErrors, ReadonlyArray.map(formatError), concat);
|
||||
};
|
||||
|
||||
export default errorsToRecord;
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './io-ts';
|
||||
export * from './types';
|
||||
32
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/io-ts.ts
generated
vendored
Normal file
32
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/io-ts.ts
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import * as Either from 'fp-ts/Either';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
import errorsToRecord from './errorsToRecord';
|
||||
import { Resolver } from './types';
|
||||
|
||||
export const ioTsResolver: Resolver = (codec) => (values, _context, options) =>
|
||||
pipe(
|
||||
values,
|
||||
codec.decode,
|
||||
Either.mapLeft(
|
||||
errorsToRecord(
|
||||
!options.shouldUseNativeValidation && options.criteriaMode === 'all',
|
||||
),
|
||||
),
|
||||
Either.mapLeft((errors) => toNestErrors(errors, options)),
|
||||
Either.fold(
|
||||
(errors) => ({
|
||||
values: {},
|
||||
errors,
|
||||
}),
|
||||
(values) => {
|
||||
options.shouldUseNativeValidation &&
|
||||
validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
values,
|
||||
errors: {},
|
||||
} as any;
|
||||
},
|
||||
),
|
||||
);
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/types.ts
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/io-ts/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as t from 'io-ts';
|
||||
import {
|
||||
FieldError,
|
||||
FieldValues,
|
||||
ResolverOptions,
|
||||
ResolverResult,
|
||||
} from 'react-hook-form';
|
||||
|
||||
export type Resolver = <T, TFieldValues extends FieldValues, TContext>(
|
||||
codec: t.Decoder<FieldValues, T>,
|
||||
) => (
|
||||
values: TFieldValues,
|
||||
_context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => ResolverResult<TFieldValues>;
|
||||
|
||||
export type ErrorObject = Record<string, FieldError>;
|
||||
export type FieldErrorWithPath = FieldError & { path: string };
|
||||
17
install/config-ui/node_modules/@hookform/resolvers/joi/package.json
generated
vendored
Normal file
17
install/config-ui/node_modules/@hookform/resolvers/joi/package.json
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/joi",
|
||||
"amdName": "hookformResolversJoi",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: joi",
|
||||
"main": "dist/joi.js",
|
||||
"module": "dist/joi.module.js",
|
||||
"umd:main": "dist/joi.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"
|
||||
}
|
||||
}
|
||||
85
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
85
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import * as Joi from 'joi';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { joiResolver } from '..';
|
||||
|
||||
const schema = Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: joiResolver(schema),
|
||||
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 Joi", 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" is not allowed to be empty',
|
||||
);
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe(
|
||||
'"password" is not allowed to be empty',
|
||||
);
|
||||
|
||||
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('');
|
||||
});
|
||||
59
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/Form.tsx
generated
vendored
Normal file
59
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import * as Joi from 'joi';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { joiResolver } from '..';
|
||||
|
||||
const schema = Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: joiResolver(schema), // 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 Joi 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 not allowed to be empty/i),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/"password" is not allowed to be empty/i),
|
||||
).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
85
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
85
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = Joi.object({
|
||||
username: Joi.string().alphanum().min(3).max(30).required(),
|
||||
password: Joi.string()
|
||||
.pattern(new RegExp('.*[A-Z].*'), 'One uppercase character')
|
||||
.pattern(new RegExp('.*[a-z].*'), 'One lowercase character')
|
||||
.pattern(new RegExp('.*\\d.*'), 'One number')
|
||||
.pattern(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
'One special character',
|
||||
)
|
||||
.min(8)
|
||||
.required(),
|
||||
repeatPassword: Joi.ref('password'),
|
||||
accessToken: [Joi.string(), Joi.number()],
|
||||
birthYear: Joi.number().integer().min(1900).max(2013),
|
||||
email: Joi.string().email({
|
||||
minDomainSegments: 2,
|
||||
tlds: { allow: ['com', 'net'] },
|
||||
}),
|
||||
tags: Joi.array().items(Joi.string()).required(),
|
||||
enabled: Joi.boolean().required(),
|
||||
like: Joi.array()
|
||||
.items(
|
||||
Joi.object({ id: Joi.number(), name: Joi.string().length(4).regex(/a/) }),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
interface Data {
|
||||
username: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
accessToken?: number | string;
|
||||
birthYear?: number;
|
||||
email?: string;
|
||||
tags: string[];
|
||||
enabled: boolean;
|
||||
like: { id: number; name: string }[];
|
||||
}
|
||||
|
||||
export const validData: Data = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z', name: 'r' }],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
293
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/__snapshots__/joi.ts.snap
generated
vendored
Normal file
293
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/__snapshots__/joi.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`joiResolver > should return a single error from joiResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": ""birthYear" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
},
|
||||
"email": {
|
||||
"message": ""email" is not allowed to be empty",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "string.empty",
|
||||
},
|
||||
"enabled": {
|
||||
"message": ""enabled" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": ""like[0].id" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
},
|
||||
"name": {
|
||||
"message": ""like[0].name" length must be 4 characters long",
|
||||
"ref": undefined,
|
||||
"type": "string.length",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": ""password" with value "___" fails to match the One uppercase character pattern",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "string.pattern.name",
|
||||
},
|
||||
"tags": {
|
||||
"message": ""tags" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
},
|
||||
"username": {
|
||||
"message": ""username" is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "any.required",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`joiResolver > should return a single error from joiResolver with \`mode: sync\` when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": ""birthYear" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
},
|
||||
"email": {
|
||||
"message": ""email" is not allowed to be empty",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "string.empty",
|
||||
},
|
||||
"enabled": {
|
||||
"message": ""enabled" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": ""like[0].id" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
},
|
||||
"name": {
|
||||
"message": ""like[0].name" length must be 4 characters long",
|
||||
"ref": undefined,
|
||||
"type": "string.length",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": ""password" with value "___" fails to match the One uppercase character pattern",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "string.pattern.name",
|
||||
},
|
||||
"tags": {
|
||||
"message": ""tags" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
},
|
||||
"username": {
|
||||
"message": ""username" is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "any.required",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`joiResolver > should return all the errors from joiResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": ""birthYear" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
"types": {
|
||||
"number.base": ""birthYear" must be a number",
|
||||
},
|
||||
},
|
||||
"email": {
|
||||
"message": ""email" is not allowed to be empty",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "string.empty",
|
||||
"types": {
|
||||
"string.empty": ""email" is not allowed to be empty",
|
||||
},
|
||||
},
|
||||
"enabled": {
|
||||
"message": ""enabled" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""enabled" is required",
|
||||
},
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": ""like[0].id" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
"types": {
|
||||
"number.base": ""like[0].id" must be a number",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
"message": ""like[0].name" length must be 4 characters long",
|
||||
"ref": undefined,
|
||||
"type": "string.length",
|
||||
"types": {
|
||||
"string.length": ""like[0].name" length must be 4 characters long",
|
||||
"string.pattern.base": ""like[0].name" with value "r" fails to match the required pattern: /a/",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": ""password" with value "___" fails to match the One uppercase character pattern",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "string.pattern.name",
|
||||
"types": {
|
||||
"string.min": ""password" length must be at least 8 characters long",
|
||||
"string.pattern.name": [
|
||||
""password" with value "___" fails to match the One uppercase character pattern",
|
||||
""password" with value "___" fails to match the One lowercase character pattern",
|
||||
""password" with value "___" fails to match the One number pattern",
|
||||
],
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
"message": ""tags" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""tags" is required",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": ""username" is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""username" is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`joiResolver > should return all the errors from joiResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": ""birthYear" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
"types": {
|
||||
"number.base": ""birthYear" must be a number",
|
||||
},
|
||||
},
|
||||
"email": {
|
||||
"message": ""email" is not allowed to be empty",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "string.empty",
|
||||
"types": {
|
||||
"string.empty": ""email" is not allowed to be empty",
|
||||
},
|
||||
},
|
||||
"enabled": {
|
||||
"message": ""enabled" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""enabled" is required",
|
||||
},
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": ""like[0].id" must be a number",
|
||||
"ref": undefined,
|
||||
"type": "number.base",
|
||||
"types": {
|
||||
"number.base": ""like[0].id" must be a number",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
"message": ""like[0].name" length must be 4 characters long",
|
||||
"ref": undefined,
|
||||
"type": "string.length",
|
||||
"types": {
|
||||
"string.length": ""like[0].name" length must be 4 characters long",
|
||||
"string.pattern.base": ""like[0].name" with value "r" fails to match the required pattern: /a/",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": ""password" with value "___" fails to match the One uppercase character pattern",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "string.pattern.name",
|
||||
"types": {
|
||||
"string.min": ""password" length must be at least 8 characters long",
|
||||
"string.pattern.name": [
|
||||
""password" with value "___" fails to match the One uppercase character pattern",
|
||||
""password" with value "___" fails to match the One lowercase character pattern",
|
||||
""password" with value "___" fails to match the One number pattern",
|
||||
],
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
"message": ""tags" is required",
|
||||
"ref": undefined,
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""tags" is required",
|
||||
},
|
||||
},
|
||||
"username": {
|
||||
"message": ""username" is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "any.required",
|
||||
"types": {
|
||||
"any.required": ""username" is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
98
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/joi.ts
generated
vendored
Normal file
98
install/config-ui/node_modules/@hookform/resolvers/joi/src/__tests__/joi.ts
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import { joiResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('joiResolver', () => {
|
||||
it('should return values from joiResolver when validation pass', async () => {
|
||||
const validateAsyncSpy = vi.spyOn(schema, 'validateAsync');
|
||||
const validateSpy = vi.spyOn(schema, 'validate');
|
||||
|
||||
const result = await joiResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(validateSpy).not.toHaveBeenCalled();
|
||||
expect(validateAsyncSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return values from joiResolver with `mode: sync` when validation pass', async () => {
|
||||
const validateAsyncSpy = vi.spyOn(schema, 'validateAsync');
|
||||
const validateSpy = vi.spyOn(schema, 'validate');
|
||||
|
||||
const result = await joiResolver(schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(validData, undefined, { fields, shouldUseNativeValidation });
|
||||
|
||||
expect(validateAsyncSpy).not.toHaveBeenCalled();
|
||||
expect(validateSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from joiResolver when validation fails', async () => {
|
||||
const result = await joiResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return a single error from joiResolver with `mode: sync` when validation fails', async () => {
|
||||
const validateAsyncSpy = vi.spyOn(schema, 'validateAsync');
|
||||
const validateSpy = vi.spyOn(schema, 'validate');
|
||||
|
||||
const result = await joiResolver(schema, undefined, {
|
||||
mode: 'sync',
|
||||
})(invalidData, undefined, { fields, shouldUseNativeValidation });
|
||||
|
||||
expect(validateAsyncSpy).not.toHaveBeenCalled();
|
||||
expect(validateSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from joiResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
|
||||
const result = await joiResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return all the errors from joiResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => {
|
||||
const result = await joiResolver(schema, undefined, { mode: 'sync' })(
|
||||
invalidData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
criteriaMode: 'all',
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return values from joiResolver when validation pass and pass down the Joi context', async () => {
|
||||
const context = { value: 'context' };
|
||||
const validateAsyncSpy = vi.spyOn(schema, 'validateAsync');
|
||||
const validateSpy = vi.spyOn(schema, 'validate');
|
||||
|
||||
const result = await joiResolver(schema)(validData, context, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(validateSpy).not.toHaveBeenCalled();
|
||||
expect(validateAsyncSpy).toHaveBeenCalledTimes(1);
|
||||
expect(validateAsyncSpy).toHaveBeenCalledWith(validData, {
|
||||
abortEarly: false,
|
||||
context,
|
||||
});
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
});
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/joi/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/joi/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './joi';
|
||||
export * from './types';
|
||||
81
install/config-ui/node_modules/@hookform/resolvers/joi/src/joi.ts
generated
vendored
Normal file
81
install/config-ui/node_modules/@hookform/resolvers/joi/src/joi.ts
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import type { ValidationError } from 'joi';
|
||||
import { FieldError, appendErrors } from 'react-hook-form';
|
||||
import { Resolver } from './types';
|
||||
|
||||
const parseErrorSchema = (
|
||||
error: ValidationError,
|
||||
validateAllFieldCriteria: boolean,
|
||||
) =>
|
||||
error.details.length
|
||||
? error.details.reduce<Record<string, FieldError>>((previous, error) => {
|
||||
const _path = error.path.join('.');
|
||||
|
||||
if (!previous[_path]) {
|
||||
previous[_path] = { message: error.message, type: error.type };
|
||||
}
|
||||
|
||||
if (validateAllFieldCriteria) {
|
||||
const types = previous[_path].types;
|
||||
const messages = types && types[error.type!];
|
||||
|
||||
previous[_path] = appendErrors(
|
||||
_path,
|
||||
validateAllFieldCriteria,
|
||||
previous,
|
||||
error.type,
|
||||
messages
|
||||
? ([] as string[]).concat(messages as string[], error.message)
|
||||
: error.message,
|
||||
) as FieldError;
|
||||
}
|
||||
|
||||
return previous;
|
||||
}, {})
|
||||
: {};
|
||||
|
||||
export const joiResolver: Resolver =
|
||||
(
|
||||
schema,
|
||||
schemaOptions = {
|
||||
abortEarly: false,
|
||||
},
|
||||
resolverOptions = {},
|
||||
) =>
|
||||
async (values, context, options) => {
|
||||
const _schemaOptions = Object.assign({}, schemaOptions, {
|
||||
context,
|
||||
});
|
||||
|
||||
let result: Record<string, any> = {};
|
||||
if (resolverOptions.mode === 'sync') {
|
||||
result = schema.validate(values, _schemaOptions);
|
||||
} else {
|
||||
try {
|
||||
result.value = await schema.validateAsync(values, _schemaOptions);
|
||||
} catch (e) {
|
||||
result.error = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(
|
||||
parseErrorSchema(
|
||||
result.error,
|
||||
!options.shouldUseNativeValidation &&
|
||||
options.criteriaMode === 'all',
|
||||
),
|
||||
options,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
errors: {},
|
||||
values: result.value,
|
||||
};
|
||||
};
|
||||
12
install/config-ui/node_modules/@hookform/resolvers/joi/src/types.ts
generated
vendored
Normal file
12
install/config-ui/node_modules/@hookform/resolvers/joi/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { AsyncValidationOptions, Schema } from 'joi';
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
|
||||
export type Resolver = <T extends Schema>(
|
||||
schema: T,
|
||||
schemaOptions?: AsyncValidationOptions,
|
||||
factoryOptions?: { mode?: 'async' | 'sync' },
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => Promise<ResolverResult<TFieldValues>>;
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/nope/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/nope/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/nope",
|
||||
"amdName": "hookformResolversNope",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: nope",
|
||||
"main": "dist/nope.js",
|
||||
"module": "dist/nope.module.js",
|
||||
"umd:main": "dist/nope.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",
|
||||
"nope-validator": "^0.12.0"
|
||||
}
|
||||
}
|
||||
85
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
85
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import Nope from 'nope-validator';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { nopeResolver } from '..';
|
||||
|
||||
const USERNAME_REQUIRED_MESSAGE = 'username field is required';
|
||||
const PASSWORD_REQUIRED_MESSAGE = 'password field is required';
|
||||
|
||||
const schema = Nope.object().shape({
|
||||
username: Nope.string().required(USERNAME_REQUIRED_MESSAGE),
|
||||
password: Nope.string().required(PASSWORD_REQUIRED_MESSAGE),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
unusedProperty: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: nopeResolver(schema),
|
||||
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 Nope", 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_REQUIRED_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('');
|
||||
});
|
||||
55
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/Form.tsx
generated
vendored
Normal file
55
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import user from '@testing-library/user-event';
|
||||
import Nope from 'nope-validator';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { nopeResolver } from '..';
|
||||
|
||||
const schema = Nope.object().shape({
|
||||
username: Nope.string().required(),
|
||||
password: Nope.string().required(),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
unusedProperty: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: nopeResolver(schema), // 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 Yup 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.getAllByText(/This field is required/i)).toHaveLength(2);
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
70
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
70
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import Nope from 'nope-validator';
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
|
||||
export const schema = Nope.object().shape({
|
||||
username: Nope.string().regex(/^\w+$/).min(2).max(30).required(),
|
||||
password: Nope.string()
|
||||
.regex(new RegExp('.*[A-Z].*'), 'One uppercase character')
|
||||
.regex(new RegExp('.*[a-z].*'), 'One lowercase character')
|
||||
.regex(new RegExp('.*\\d.*'), 'One number')
|
||||
.regex(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
'One special character',
|
||||
)
|
||||
.min(8, 'Must be at least 8 characters in length')
|
||||
.required('New Password is required'),
|
||||
repeatPassword: Nope.string()
|
||||
.oneOf([Nope.ref('password')], "Passwords don't match")
|
||||
.required(),
|
||||
accessToken: Nope.string(),
|
||||
birthYear: Nope.number().min(1900).max(2013),
|
||||
email: Nope.string().email(),
|
||||
tags: Nope.array().of(Nope.string()).required(),
|
||||
enabled: Nope.boolean(),
|
||||
like: Nope.object().shape({
|
||||
id: Nope.number().required(),
|
||||
name: Nope.string().atLeast(4).required(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const validData = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
accessToken: 'accessToken',
|
||||
like: {
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: { id: 'z' },
|
||||
tags: [1, 2, 3],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
43
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/__snapshots__/nope.ts.snap
generated
vendored
Normal file
43
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/__snapshots__/nope.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`nopeResolver > should return a single error from nopeResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "The field is not a valid number",
|
||||
"ref": undefined,
|
||||
},
|
||||
"like": {
|
||||
"id": {
|
||||
"message": "The field is not a valid number",
|
||||
"ref": undefined,
|
||||
},
|
||||
"name": {
|
||||
"message": "This field is required",
|
||||
"ref": undefined,
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "One uppercase character",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "This field is required",
|
||||
"ref": undefined,
|
||||
},
|
||||
"tags": {
|
||||
"message": "One or more elements are of invalid type",
|
||||
"ref": undefined,
|
||||
},
|
||||
"username": {
|
||||
"message": "This field is required",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
28
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/nope.ts
generated
vendored
Normal file
28
install/config-ui/node_modules/@hookform/resolvers/nope/src/__tests__/nope.ts
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
|
||||
import { nopeResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('nopeResolver', () => {
|
||||
it('should return values from nopeResolver when validation pass', async () => {
|
||||
const schemaSpy = vi.spyOn(schema, 'validate');
|
||||
|
||||
const result = await nopeResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(schemaSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from nopeResolver when validation fails', async () => {
|
||||
const result = await nopeResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/nope/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/nope/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './nope';
|
||||
export * from './types';
|
||||
46
install/config-ui/node_modules/@hookform/resolvers/nope/src/nope.ts
generated
vendored
Normal file
46
install/config-ui/node_modules/@hookform/resolvers/nope/src/nope.ts
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import type { ShapeErrors } from 'nope-validator/lib/cjs/types';
|
||||
import type { FieldError, FieldErrors } from 'react-hook-form';
|
||||
import type { Resolver } from './types';
|
||||
|
||||
const parseErrors = (
|
||||
errors: ShapeErrors,
|
||||
parsedErrors: FieldErrors = {},
|
||||
path = '',
|
||||
) => {
|
||||
return Object.keys(errors).reduce((acc, key) => {
|
||||
const _path = path ? `${path}.${key}` : key;
|
||||
const error = errors[key];
|
||||
|
||||
if (typeof error === 'string') {
|
||||
acc[_path] = {
|
||||
message: error,
|
||||
} as FieldError;
|
||||
} else {
|
||||
parseErrors(error, acc, _path);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, parsedErrors);
|
||||
};
|
||||
|
||||
export const nopeResolver: Resolver =
|
||||
(
|
||||
schema,
|
||||
schemaOptions = {
|
||||
abortEarly: false,
|
||||
},
|
||||
) =>
|
||||
(values, context, options) => {
|
||||
const result = schema.validate(values, context, schemaOptions) as
|
||||
| ShapeErrors
|
||||
| undefined;
|
||||
|
||||
if (result) {
|
||||
return { values: {}, errors: toNestErrors(parseErrors(result), options) };
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return { values, errors: {} };
|
||||
};
|
||||
19
install/config-ui/node_modules/@hookform/resolvers/nope/src/types.ts
generated
vendored
Normal file
19
install/config-ui/node_modules/@hookform/resolvers/nope/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { NopeObject } from 'nope-validator/lib/cjs/NopeObject';
|
||||
import type {
|
||||
FieldValues,
|
||||
ResolverOptions,
|
||||
ResolverResult,
|
||||
} from 'react-hook-form';
|
||||
|
||||
type ValidateOptions = Parameters<NopeObject['validate']>[2];
|
||||
type Context = Parameters<NopeObject['validate']>[1];
|
||||
|
||||
export type Resolver = <T extends NopeObject>(
|
||||
schema: T,
|
||||
schemaOptions?: ValidateOptions,
|
||||
resolverOptions?: { mode?: 'async' | 'sync'; rawValues?: boolean },
|
||||
) => <TFieldValues extends FieldValues, TContext extends Context>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => ResolverResult<TFieldValues>;
|
||||
310
install/config-ui/node_modules/@hookform/resolvers/package.json
generated
vendored
Normal file
310
install/config-ui/node_modules/@hookform/resolvers/package.json
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
{
|
||||
"name": "@hookform/resolvers",
|
||||
"amdName": "hookformResolvers",
|
||||
"version": "3.10.0",
|
||||
"description": "React Hook Form validation resolvers: Yup, Joi, Superstruct, Zod, Vest, Class Validator, io-ts, Nope, computed-types, TypeBox, arktype, Typanion, Effect-TS and VineJS",
|
||||
"main": "dist/resolvers.js",
|
||||
"module": "dist/resolvers.module.js",
|
||||
"umd:main": "dist/resolvers.umd.js",
|
||||
"source": "src/index.ts",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"umd": "./dist/resolvers.umd.js",
|
||||
"import": "./dist/resolvers.mjs",
|
||||
"require": "./dist/resolvers.js"
|
||||
},
|
||||
"./zod": {
|
||||
"types": "./zod/dist/index.d.ts",
|
||||
"umd": "./zod/dist/zod.umd.js",
|
||||
"import": "./zod/dist/zod.mjs",
|
||||
"require": "./zod/dist/zod.js"
|
||||
},
|
||||
"./typebox": {
|
||||
"types": "./typebox/dist/index.d.ts",
|
||||
"umd": "./typebox/dist/typebox.umd.js",
|
||||
"import": "./typebox/dist/typebox.mjs",
|
||||
"require": "./typebox/dist/typebox.js"
|
||||
},
|
||||
"./yup": {
|
||||
"types": "./yup/dist/index.d.ts",
|
||||
"umd": "./yup/dist/yup.umd.js",
|
||||
"import": "./yup/dist/yup.mjs",
|
||||
"require": "./yup/dist/yup.js"
|
||||
},
|
||||
"./joi": {
|
||||
"types": "./joi/dist/index.d.ts",
|
||||
"umd": "./joi/dist/joi.umd.js",
|
||||
"import": "./joi/dist/joi.mjs",
|
||||
"require": "./joi/dist/joi.js"
|
||||
},
|
||||
"./vest": {
|
||||
"types": "./vest/dist/index.d.ts",
|
||||
"umd": "./vest/dist/vest.umd.js",
|
||||
"import": "./vest/dist/vest.mjs",
|
||||
"require": "./vest/dist/vest.js"
|
||||
},
|
||||
"./superstruct": {
|
||||
"types": "./superstruct/dist/index.d.ts",
|
||||
"umd": "./superstruct/dist/superstruct.umd.js",
|
||||
"import": "./superstruct/dist/superstruct.mjs",
|
||||
"require": "./superstruct/dist/superstruct.js"
|
||||
},
|
||||
"./class-validator": {
|
||||
"types": "./class-validator/dist/index.d.ts",
|
||||
"umd": "./class-validator/dist/class-validator.umd.js",
|
||||
"import": "./class-validator/dist/class-validator.mjs",
|
||||
"require": "./class-validator/dist/class-validator.js"
|
||||
},
|
||||
"./io-ts": {
|
||||
"types": "./io-ts/dist/index.d.ts",
|
||||
"umd": "./io-ts/dist/io-ts.umd.js",
|
||||
"import": "./io-ts/dist/io-ts.mjs",
|
||||
"require": "./io-ts/dist/io-ts.js"
|
||||
},
|
||||
"./nope": {
|
||||
"types": "./nope/dist/index.d.ts",
|
||||
"umd": "./nope/dist/nope.umd.js",
|
||||
"import": "./nope/dist/nope.mjs",
|
||||
"require": "./nope/dist/nope.js"
|
||||
},
|
||||
"./computed-types": {
|
||||
"types": "./computed-types/dist/index.d.ts",
|
||||
"umd": "./computed-types/dist/computed-types.umd.js",
|
||||
"import": "./computed-types/dist/computed-types.mjs",
|
||||
"require": "./computed-types/dist/computed-types.js"
|
||||
},
|
||||
"./typanion": {
|
||||
"types": "./typanion/dist/index.d.ts",
|
||||
"umd": "./typanion/dist/typanion.umd.js",
|
||||
"import": "./typanion/dist/typanion.mjs",
|
||||
"require": "./typanion/dist/typanion.js"
|
||||
},
|
||||
"./ajv": {
|
||||
"types": "./ajv/dist/index.d.ts",
|
||||
"umd": "./ajv/dist/ajv.umd.js",
|
||||
"import": "./ajv/dist/ajv.mjs",
|
||||
"require": "./ajv/dist/ajv.js"
|
||||
},
|
||||
"./arktype": {
|
||||
"types": "./arktype/dist/index.d.ts",
|
||||
"umd": "./arktype/dist/arktype.umd.js",
|
||||
"import": "./arktype/dist/arktype.mjs",
|
||||
"require": "./arktype/dist/arktype.js"
|
||||
},
|
||||
"./valibot": {
|
||||
"types": "./valibot/dist/index.d.ts",
|
||||
"umd": "./valibot/dist/valibot.umd.js",
|
||||
"import": "./valibot/dist/valibot.mjs",
|
||||
"require": "./valibot/dist/valibot.js"
|
||||
},
|
||||
"./typeschema": {
|
||||
"types": "./typeschema/dist/index.d.ts",
|
||||
"umd": "./typeschema/dist/typeschema.umd.js",
|
||||
"import": "./typeschema/dist/typeschema.mjs",
|
||||
"require": "./typeschema/dist/typeschema.js"
|
||||
},
|
||||
"./effect-ts": {
|
||||
"types": "./effect-ts/dist/index.d.ts",
|
||||
"umd": "./effect-ts/dist/effect-ts.umd.js",
|
||||
"import": "./effect-ts/dist/effect-ts.mjs",
|
||||
"require": "./effect-ts/dist/effect-ts.js"
|
||||
},
|
||||
"./vine": {
|
||||
"types": "./vine/dist/index.d.ts",
|
||||
"umd": "./vine/dist/vine.umd.js",
|
||||
"import": "./vine/dist/vine.mjs",
|
||||
"require": "./vine/dist/vine.js"
|
||||
},
|
||||
"./fluentvalidation-ts": {
|
||||
"types": "./fluentvalidation-ts/dist/index.d.ts",
|
||||
"umd": "./fluentvalidation-ts/dist/fluentvalidation-ts.umd.js",
|
||||
"import": "./fluentvalidation-ts/dist/fluentvalidation-ts.mjs",
|
||||
"require": "./fluentvalidation-ts/dist/fluentvalidation-ts.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./*": "./*"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"yup/package.json",
|
||||
"yup/src",
|
||||
"yup/dist",
|
||||
"zod/package.json",
|
||||
"zod/src",
|
||||
"zod/dist",
|
||||
"vest/package.json",
|
||||
"vest/src",
|
||||
"vest/dist",
|
||||
"joi/package.json",
|
||||
"joi/src",
|
||||
"joi/dist",
|
||||
"superstruct/package.json",
|
||||
"superstruct/src",
|
||||
"superstruct/dist",
|
||||
"class-validator/package.json",
|
||||
"class-validator/src",
|
||||
"class-validator/dist",
|
||||
"io-ts/package.json",
|
||||
"io-ts/src",
|
||||
"io-ts/dist",
|
||||
"nope/package.json",
|
||||
"nope/src",
|
||||
"nope/dist",
|
||||
"computed-types/package.json",
|
||||
"computed-types/src",
|
||||
"computed-types/dist",
|
||||
"typanion/package.json",
|
||||
"typanion/src",
|
||||
"typanion/dist",
|
||||
"ajv/package.json",
|
||||
"ajv/src",
|
||||
"ajv/dist",
|
||||
"typebox/package.json",
|
||||
"typebox/src",
|
||||
"typebox/dist",
|
||||
"arktype/package.json",
|
||||
"arktype/src",
|
||||
"arktype/dist",
|
||||
"valibot/package.json",
|
||||
"valibot/src",
|
||||
"valibot/dist",
|
||||
"typeschema/package.json",
|
||||
"typeschema/src",
|
||||
"typeschema/dist",
|
||||
"effect-ts/package.json",
|
||||
"effect-ts/src",
|
||||
"effect-ts/dist",
|
||||
"effect-ts/package.json",
|
||||
"effect-ts/src",
|
||||
"effect-ts/dist",
|
||||
"vine/package.json",
|
||||
"vine/src",
|
||||
"vine/dist",
|
||||
"fluentvalidation-ts/package.json",
|
||||
"fluentvalidation-ts/src",
|
||||
"fluentvalidation-ts/dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "run-s build:src",
|
||||
"build": "cross-env npm-run-all --parallel 'build:*'",
|
||||
"build:src": "microbundle build --globals react-hook-form=ReactHookForm",
|
||||
"build:zod": "microbundle --cwd zod --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:yup": "microbundle --cwd yup --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:joi": "microbundle --cwd joi --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:superstruct": "microbundle --cwd superstruct --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:io-ts": "microbundle --cwd io-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,fp-ts/Either=Either,fp-ts/function=_function,fp-ts/Option=Option,fp-ts/ReadonlyArray=ReadonlyArray,fp-ts/Semigroup=Semigroup,fp-ts/ReadonlyRecord=ReadonlyRecord",
|
||||
"build:vest": "microbundle --cwd vest --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,vest/promisify=promisify",
|
||||
"build:class-validator": "microbundle --cwd class-validator --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:nope": "microbundle --cwd nope --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:computed-types": "microbundle --cwd computed-types --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:typanion": "microbundle --cwd typanion --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:ajv": "microbundle --cwd ajv --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:typebox": "microbundle --cwd typebox --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@sinclair/typebox/value=value,@sinclair/typebox/compiler=compiler",
|
||||
"build:arktype": "microbundle --cwd arktype --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:valibot": "microbundle --cwd valibot --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"build:typeschema": "microbundle --cwd typeschema --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@typeschema/main=main",
|
||||
"build:effect-ts": "microbundle --cwd effect-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,effect=Effect,effect/SchemaAST=EffectSchemaAST,effect/ParseResult=EffectParseResult",
|
||||
"build:vine": "microbundle --cwd vine --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@vinejs/vine=vine",
|
||||
"build:fluentvalidation-ts": "microbundle --cwd fluentvalidation-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
|
||||
"postbuild": "node ./config/node-13-exports.js && check-export-map",
|
||||
"lint": "bunx @biomejs/biome check --write --vcs-use-ignore-file=true .",
|
||||
"lint:types": "tsc",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch",
|
||||
"check:all": "npm-run-all --parallel lint:* test",
|
||||
"csb:install": "pnpx replace-json-property@1.9.0 package.json prepare \"node -e 'process.exit(0)'\" && pnpm i --no-frozen-lockfile",
|
||||
"csb:build": "cross-env npm-run-all --sequential 'build:*'"
|
||||
},
|
||||
"keywords": [
|
||||
"scheme",
|
||||
"validation",
|
||||
"scheme-validation",
|
||||
"hookform",
|
||||
"react-hook-form",
|
||||
"yup",
|
||||
"joi",
|
||||
"superstruct",
|
||||
"typescript",
|
||||
"zod",
|
||||
"vest",
|
||||
"class-validator",
|
||||
"io-ts",
|
||||
"effect-ts",
|
||||
"nope",
|
||||
"computed-types",
|
||||
"typanion",
|
||||
"ajv",
|
||||
"TypeBox",
|
||||
"arktype",
|
||||
"typeschema",
|
||||
"vine",
|
||||
"fluentvalidation-ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/react-hook-form/resolvers.git"
|
||||
},
|
||||
"author": "bluebill1049 <bluebill1049@hotmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-hook-form/resolvers/issues"
|
||||
},
|
||||
"homepage": "https://react-hook-form.com",
|
||||
"devDependencies": {
|
||||
"@sinclair/typebox": "^0.32.34",
|
||||
"@testing-library/dom": "^10.3.1",
|
||||
"@testing-library/jest-dom": "^6.4.6",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/node": "^20.14.9",
|
||||
"@types/react": "^18.3.3",
|
||||
"@typeschema/core": "^0.13.2",
|
||||
"@typeschema/main": "^0.13.10",
|
||||
"@typeschema/zod": "^0.13.3",
|
||||
"@vinejs/vine": "^2.1.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"ajv": "^8.16.0",
|
||||
"ajv-errors": "^3.0.0",
|
||||
"arktype": "2.0.0-dev.26",
|
||||
"check-export-map": "^1.3.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"computed-types": "^1.11.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"effect": "^3.10.3",
|
||||
"fluentvalidation-ts": "^3.2.0",
|
||||
"fp-ts": "^2.16.7",
|
||||
"io-ts": "^2.2.21",
|
||||
"io-ts-types": "^0.5.19",
|
||||
"joi": "^17.13.3",
|
||||
"jsdom": "^24.1.0",
|
||||
"lefthook": "^1.6.18",
|
||||
"microbundle": "^0.15.1",
|
||||
"monocle-ts": "^2.3.13",
|
||||
"newtype-ts": "^0.3.5",
|
||||
"nope-validator": "^1.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"superstruct": "^1.0.4",
|
||||
"typanion": "^3.14.0",
|
||||
"typescript": "^5.5.3",
|
||||
"valibot": "^1.0.0-beta.0",
|
||||
"vest": "^5.3.0",
|
||||
"vite": "^5.3.3",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-hook-form": "^7.0.0"
|
||||
}
|
||||
}
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/superstruct/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/superstruct/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/superstruct",
|
||||
"amdName": "hookformResolversSuperstruct",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: superstruct",
|
||||
"main": "dist/superstruct.js",
|
||||
"module": "dist/superstruct.module.js",
|
||||
"umd:main": "dist/superstruct.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",
|
||||
"superstruct": ">=0.12.0"
|
||||
}
|
||||
}
|
||||
82
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
82
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
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 { Infer, object, size, string } from 'superstruct';
|
||||
import { superstructResolver } from '..';
|
||||
|
||||
const schema = object({
|
||||
username: size(string(), 2),
|
||||
password: size(string(), 6),
|
||||
});
|
||||
|
||||
type FormData = Infer<typeof schema>;
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: superstructResolver(schema),
|
||||
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 Superstruct", 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(
|
||||
'Expected a string with a length of `2` but received one with a length of `0`',
|
||||
);
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe(
|
||||
'Expected a string with a length of `6` but received one with a length of `0`',
|
||||
);
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/username/i), 'jo');
|
||||
await user.type(screen.getByPlaceholderText(/password/i), 'passwo');
|
||||
|
||||
// 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
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/Form.tsx
generated
vendored
Normal file
60
install/config-ui/node_modules/@hookform/resolvers/superstruct/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 { Infer, object, size, string } from 'superstruct';
|
||||
import { superstructResolver } from '..';
|
||||
|
||||
const schema = object({
|
||||
username: size(string(), 2),
|
||||
password: size(string(), 6),
|
||||
});
|
||||
|
||||
type FormData = Infer<typeof schema>;
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: superstructResolver(schema), // 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 Superstruct 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(
|
||||
/Expected a string with a length of `2` but received one with a length of `0`/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Expected a string with a length of `6` but received one with a length of `0`/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
75
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
75
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
import {
|
||||
Infer,
|
||||
array,
|
||||
boolean,
|
||||
define,
|
||||
max,
|
||||
min,
|
||||
number,
|
||||
object,
|
||||
optional,
|
||||
pattern,
|
||||
size,
|
||||
string,
|
||||
union,
|
||||
} from 'superstruct';
|
||||
|
||||
const Password = define(
|
||||
'Password',
|
||||
(value, ctx) => value === ctx.branch[0].password,
|
||||
);
|
||||
|
||||
export const schema = object({
|
||||
username: size(pattern(string(), /^\w+$/), 3, 30),
|
||||
password: pattern(string(), /^[a-zA-Z0-9]{3,30}/),
|
||||
repeatPassword: Password,
|
||||
accessToken: optional(union([string(), number()])),
|
||||
birthYear: optional(max(min(number(), 1900), 2013)),
|
||||
email: optional(pattern(string(), /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)),
|
||||
tags: array(string()),
|
||||
enabled: boolean(),
|
||||
like: optional(array(object({ id: number(), name: size(string(), 4) }))),
|
||||
});
|
||||
|
||||
export const validData: Infer<typeof schema> = {
|
||||
username: 'Doe',
|
||||
password: 'Password123',
|
||||
repeatPassword: 'Password123',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
like: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: [{ id: 'z' }],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
64
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/__snapshots__/superstruct.ts.snap
generated
vendored
Normal file
64
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/__snapshots__/superstruct.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`superstructResolver > should return a single error from superstructResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"birthYear": {
|
||||
"message": "Expected a number, but received: "birthYear"",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"email": {
|
||||
"message": "Expected a string matching \`/^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$/\` but received """,
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
"enabled": {
|
||||
"message": "Expected a value of type \`boolean\`, but received: \`undefined\`",
|
||||
"ref": undefined,
|
||||
"type": "boolean",
|
||||
},
|
||||
"like": [
|
||||
{
|
||||
"id": {
|
||||
"message": "Expected a number, but received: "z"",
|
||||
"ref": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"name": {
|
||||
"message": "Expected a string, but received: undefined",
|
||||
"ref": undefined,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"password": {
|
||||
"message": "Expected a string matching \`/^[a-zA-Z0-9]{3,30}/\` but received "___"",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Expected a value of type \`Password\`, but received: \`undefined\`",
|
||||
"ref": undefined,
|
||||
"type": "Password",
|
||||
},
|
||||
"tags": {
|
||||
"message": "Expected an array value, but received: undefined",
|
||||
"ref": undefined,
|
||||
"type": "array",
|
||||
},
|
||||
"username": {
|
||||
"message": "Expected a string, but received: undefined",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
37
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/superstruct.ts
generated
vendored
Normal file
37
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/__tests__/superstruct.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { superstructResolver } from '..';
|
||||
import { fields, invalidData, schema, validData } from './__fixtures__/data';
|
||||
|
||||
const shouldUseNativeValidation = false;
|
||||
|
||||
describe('superstructResolver', () => {
|
||||
it('should return values from superstructResolver when validation pass', async () => {
|
||||
const result = await superstructResolver(schema)(validData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
|
||||
it('should return a single error from superstructResolver when validation fails', async () => {
|
||||
const result = await superstructResolver(schema)(invalidData, undefined, {
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return values from superstructResolver when validation pass & raw=true', async () => {
|
||||
const result = await superstructResolver(schema, undefined, { raw: true })(
|
||||
validData,
|
||||
undefined,
|
||||
{
|
||||
fields,
|
||||
shouldUseNativeValidation,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({ errors: {}, values: validData });
|
||||
});
|
||||
});
|
||||
2
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/index.ts
generated
vendored
Normal file
2
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './superstruct';
|
||||
export * from './types';
|
||||
35
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/superstruct.ts
generated
vendored
Normal file
35
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/superstruct.ts
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
|
||||
import { FieldError } from 'react-hook-form';
|
||||
|
||||
import { StructError, validate } from 'superstruct';
|
||||
import { Resolver } from './types';
|
||||
|
||||
const parseErrorSchema = (error: StructError) =>
|
||||
error.failures().reduce<Record<string, FieldError>>(
|
||||
(previous, error) =>
|
||||
(previous[error.path.join('.')] = {
|
||||
message: error.message,
|
||||
type: error.type,
|
||||
}) && previous,
|
||||
{},
|
||||
);
|
||||
|
||||
export const superstructResolver: Resolver =
|
||||
(schema, schemaOptions, resolverOptions = {}) =>
|
||||
(values, _, options) => {
|
||||
const result = validate(values, schema, schemaOptions);
|
||||
|
||||
if (result[0]) {
|
||||
return {
|
||||
values: {},
|
||||
errors: toNestErrors(parseErrorSchema(result[0]), options),
|
||||
};
|
||||
}
|
||||
|
||||
options.shouldUseNativeValidation && validateFieldsNatively({}, options);
|
||||
|
||||
return {
|
||||
values: resolverOptions.raw ? values : result[1],
|
||||
errors: {},
|
||||
};
|
||||
};
|
||||
20
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/types.ts
generated
vendored
Normal file
20
install/config-ui/node_modules/@hookform/resolvers/superstruct/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
|
||||
import { Struct, validate } from 'superstruct';
|
||||
|
||||
type Options = Parameters<typeof validate>[2];
|
||||
|
||||
export type Resolver = <T extends Struct<any, any>>(
|
||||
schema: T,
|
||||
options?: Options,
|
||||
factoryOptions?: {
|
||||
/**
|
||||
* Return the raw input values rather than the parsed values.
|
||||
* @default false
|
||||
*/
|
||||
raw?: boolean;
|
||||
},
|
||||
) => <TFieldValues extends FieldValues, TContext>(
|
||||
values: TFieldValues,
|
||||
context: TContext | undefined,
|
||||
options: ResolverOptions<TFieldValues>,
|
||||
) => ResolverResult<TFieldValues>;
|
||||
18
install/config-ui/node_modules/@hookform/resolvers/typanion/package.json
generated
vendored
Normal file
18
install/config-ui/node_modules/@hookform/resolvers/typanion/package.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@hookform/resolvers/typanion",
|
||||
"amdName": "hookformResolversTypanion",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "React Hook Form validation resolver: typanion",
|
||||
"main": "dist/typanion.js",
|
||||
"module": "dist/typanion.module.js",
|
||||
"umd:main": "dist/typanion.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",
|
||||
"typanion": "^3.3.2"
|
||||
}
|
||||
}
|
||||
85
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
85
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/Form-native-validation.tsx
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
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 t from 'typanion';
|
||||
import { typanionResolver } from '..';
|
||||
|
||||
const ERROR_MESSAGE =
|
||||
'Expected to have a length of at least 1 elements (got 0)';
|
||||
|
||||
const schema = t.isObject({
|
||||
username: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
|
||||
password: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
unusedProperty: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const { register, handleSubmit } = useForm<FormData>({
|
||||
resolver: typanionResolver(schema),
|
||||
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 Typanion", 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(ERROR_MESSAGE);
|
||||
|
||||
// password
|
||||
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
|
||||
expect(passwordField.validity.valid).toBe(false);
|
||||
expect(passwordField.validationMessage).toBe(ERROR_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('');
|
||||
});
|
||||
59
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/Form.tsx
generated
vendored
Normal file
59
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/Form.tsx
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
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 t from 'typanion';
|
||||
import { typanionResolver } from '..';
|
||||
|
||||
const schema = t.isObject({
|
||||
username: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
|
||||
password: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
unusedProperty: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
function TestComponent({ onSubmit }: Props) {
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormData>({
|
||||
resolver: typanionResolver(schema), // 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 Typanion 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.getAllByText(
|
||||
'Expected to have a length of at least 1 elements (got 0)',
|
||||
),
|
||||
).toHaveLength(2);
|
||||
expect(handleSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
82
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
82
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/__fixtures__/data.ts
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Field, InternalFieldName } from 'react-hook-form';
|
||||
import * as t from 'typanion';
|
||||
|
||||
export const isSchema = t.isObject({
|
||||
username: t.applyCascade(t.isString(), [
|
||||
t.matchesRegExp(/^\w+$/),
|
||||
t.hasMinLength(2),
|
||||
t.hasMaxLength(30),
|
||||
]),
|
||||
password: t.applyCascade(t.isString(), [
|
||||
t.matchesRegExp(new RegExp('.*[A-Z].*')), // one uppercase character
|
||||
t.matchesRegExp(new RegExp('.*[a-z].*')), // one lowercase character
|
||||
t.matchesRegExp(new RegExp('.*\\d.*')), // one number
|
||||
t.matchesRegExp(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
), // one special character
|
||||
t.hasMinLength(8), // Must be at least 8 characters in length
|
||||
]),
|
||||
repeatPassword: t.applyCascade(t.isString(), [
|
||||
t.matchesRegExp(new RegExp('.*[A-Z].*')), // one uppercase character
|
||||
t.matchesRegExp(new RegExp('.*[a-z].*')), // one lowercase character
|
||||
t.matchesRegExp(new RegExp('.*\\d.*')), // one number
|
||||
t.matchesRegExp(
|
||||
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
||||
), // one special character
|
||||
t.hasMinLength(8), // Must be at least 8 characters in length
|
||||
]),
|
||||
accessToken: t.isString(),
|
||||
birthYear: t.applyCascade(t.isNumber(), [
|
||||
t.isInteger(),
|
||||
t.isInInclusiveRange(1900, 2013),
|
||||
]),
|
||||
email: t.applyCascade(t.isString(), [t.matchesRegExp(/^\S+@\S+$/)]),
|
||||
tags: t.isArray(t.isString()),
|
||||
enabled: t.isBoolean(),
|
||||
like: t.isObject({
|
||||
id: t.applyCascade(t.isNumber(), [t.isInteger(), t.isPositive()]),
|
||||
name: t.applyCascade(t.isString(), [t.hasMinLength(4)]),
|
||||
}),
|
||||
});
|
||||
|
||||
export const validData = {
|
||||
username: 'Doe',
|
||||
password: 'Password123_',
|
||||
repeatPassword: 'Password123_',
|
||||
birthYear: 2000,
|
||||
email: 'john@doe.com',
|
||||
tags: ['tag1', 'tag2'],
|
||||
enabled: true,
|
||||
accessToken: 'accessToken',
|
||||
like: {
|
||||
id: 1,
|
||||
name: 'name',
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidData = {
|
||||
password: '___',
|
||||
email: '',
|
||||
birthYear: 'birthYear',
|
||||
like: { id: 'z' },
|
||||
tags: [1, 2, 3],
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
67
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/__snapshots__/typanion.ts.snap
generated
vendored
Normal file
67
install/config-ui/node_modules/@hookform/resolvers/typanion/src/__tests__/__snapshots__/typanion.ts.snap
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`typanionResolver > should return a single error from typanionResolver when validation fails 1`] = `
|
||||
{
|
||||
"errors": {
|
||||
"accessToken": {
|
||||
"message": "Expected a string (got undefined)",
|
||||
"ref": undefined,
|
||||
},
|
||||
"birthYear": {
|
||||
"message": "Expected a number (got "birthYear")",
|
||||
"ref": undefined,
|
||||
},
|
||||
"email": {
|
||||
"message": "Expected to match the pattern /^\\S+@\\S+$/ (got an empty string)",
|
||||
"ref": {
|
||||
"name": "email",
|
||||
},
|
||||
},
|
||||
"enabled": {
|
||||
"message": "Expected a boolean (got undefined)",
|
||||
"ref": undefined,
|
||||
},
|
||||
"like": {
|
||||
"id": {
|
||||
"message": "Expected a number (got "z")",
|
||||
"ref": undefined,
|
||||
},
|
||||
"name": {
|
||||
"message": "Expected a string (got undefined)",
|
||||
"ref": undefined,
|
||||
},
|
||||
},
|
||||
"password": {
|
||||
"message": "Expected to match the pattern /.*[A-Z].*/ (got "___")",
|
||||
"ref": {
|
||||
"name": "password",
|
||||
},
|
||||
},
|
||||
"repeatPassword": {
|
||||
"message": "Expected a string (got undefined)",
|
||||
"ref": undefined,
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"message": "Expected a string (got 1)",
|
||||
"ref": undefined,
|
||||
},
|
||||
{
|
||||
"message": "Expected a string (got 2)",
|
||||
"ref": undefined,
|
||||
},
|
||||
{
|
||||
"message": "Expected a string (got 3)",
|
||||
"ref": undefined,
|
||||
},
|
||||
],
|
||||
"username": {
|
||||
"message": "Expected a string (got undefined)",
|
||||
"ref": {
|
||||
"name": "username",
|
||||
},
|
||||
},
|
||||
},
|
||||
"values": {},
|
||||
}
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user