@coxy/react-validator 4.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -36
- package/dist/index.js +206 -2
- package/example/example.tsx +2 -30
- package/package.json +10 -9
- package/src/context.ts +0 -1
- package/src/index.ts +1 -1
- package/src/rules.test.ts +16 -111
- package/src/rules.ts +5 -66
- package/src/types.ts +2 -8
- package/src/use-validator.test.tsx +3 -3
- package/src/use-validator.ts +1 -1
- package/src/validator-field.test.tsx +0 -26
- package/src/validator-field.tsx +1 -8
- package/src/validator-wrapper.test.tsx +6 -6
- package/src/validator-wrapper.tsx +3 -19
- package/src/validator.test.tsx +1 -1
- package/src/validator.ts +22 -17
- package/dist/index.d.mts +0 -106
- package/dist/index.d.ts +0 -106
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/src/custom-errors.test.tsx +0 -73
- package/tsup.config.ts +0 -14
package/README.md
CHANGED
|
@@ -71,41 +71,17 @@ Create your own rules easily, or use the built-in ones:
|
|
|
71
71
|
|
|
72
72
|
```javascript
|
|
73
73
|
const rules = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
password: [
|
|
79
|
-
{ rule: value => value.length >= 6, message: 'Password must be at least 6 characters' },
|
|
80
|
-
],
|
|
81
|
-
notEmpty: [
|
|
82
|
-
{ rule: value => !!value && value.trim() !== '', message: 'Field cannot be empty' },
|
|
83
|
-
],
|
|
84
|
-
boolean: [
|
|
85
|
-
{ rule: value => typeof value === 'boolean', message: 'Must be a boolean value' },
|
|
86
|
-
],
|
|
87
|
-
min: (min) => ([
|
|
88
|
-
{ rule: value => Number(value) >= min, message: `Must be at least ${min}` },
|
|
89
|
-
]),
|
|
90
|
-
max: (max) => ([
|
|
91
|
-
{ rule: value => Number(value) <= max, message: `Must be at most ${max}` },
|
|
92
|
-
]),
|
|
93
|
-
length: (min, max) => ([
|
|
94
|
-
{ rule: value => value.length >= min, message: `Must be at least ${min} characters` },
|
|
95
|
-
...(max ? [{ rule: value => value.length <= max, message: `Must be at most ${max} characters` }] : [])
|
|
96
|
-
]),
|
|
97
|
-
};
|
|
74
|
+
notEmpty: [z.string().min(1, { error: 'Field is required' })],
|
|
75
|
+
isTrue: [z.boolean({ error: 'Value is required' }).and(z.literal(true))],
|
|
76
|
+
email: [z.string().min(1, { error: 'Email is required' }), z.email({ message: 'Email is invalid' })],
|
|
77
|
+
}
|
|
98
78
|
```
|
|
99
79
|
|
|
100
|
-
| Name
|
|
101
|
-
|
|
102
|
-
| email
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
| boolean | Array | Ensure a boolean value is present. |
|
|
106
|
-
| min(n) | Function | Validate number is >= `n`. |
|
|
107
|
-
| max(n) | Function | Validate number is <= `n`. |
|
|
108
|
-
| length(a,b) | Function | Validate string length is between `a` and `b` (optional). |
|
|
80
|
+
| Name | Type | Description |
|
|
81
|
+
|----------|-------|--------------------------------------------|
|
|
82
|
+
| email | Array | Validate non-empty email with regex check. |
|
|
83
|
+
| notEmpty | Array | Check if a string is not empty. |
|
|
84
|
+
| isTrue | Array | Ensure a boolean value is present. |
|
|
109
85
|
|
|
110
86
|
---
|
|
111
87
|
|
|
@@ -160,7 +136,7 @@ Use it server-side or in custom flows.
|
|
|
160
136
|
const validator = new Validator({ stopAtFirstError: true });
|
|
161
137
|
|
|
162
138
|
const field = validator.addField({
|
|
163
|
-
rules: rules.
|
|
139
|
+
rules: rules.email,
|
|
164
140
|
value: '12345',
|
|
165
141
|
});
|
|
166
142
|
```
|
|
@@ -200,8 +176,8 @@ validator.addField({
|
|
|
200
176
|
|
|
201
177
|
validator.addField({
|
|
202
178
|
id: 'password',
|
|
203
|
-
rules: rules.
|
|
204
|
-
value:
|
|
179
|
+
rules: rules.isTrue,
|
|
180
|
+
value: true,
|
|
205
181
|
});
|
|
206
182
|
|
|
207
183
|
const result = validator.validate();
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,206 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
Validator: () => Validator,
|
|
23
|
+
ValidatorField: () => ValidatorField,
|
|
24
|
+
ValidatorWrapper: () => ValidatorWrapper,
|
|
25
|
+
rules: () => rules,
|
|
26
|
+
useValidator: () => useValidator
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/rules.ts
|
|
31
|
+
var import_zod = require("zod");
|
|
32
|
+
var rules = {
|
|
33
|
+
notEmpty: [import_zod.z.string().min(1, { error: "Field is required" })],
|
|
34
|
+
isTrue: [import_zod.z.boolean({ error: "Value is required" }).and(import_zod.z.literal(true))],
|
|
35
|
+
email: [import_zod.z.string().min(1, { error: "Email is required" }), import_zod.z.email({ message: "Email is invalid" })]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/validator.ts
|
|
39
|
+
var Field = class {
|
|
40
|
+
rules;
|
|
41
|
+
required;
|
|
42
|
+
value;
|
|
43
|
+
id;
|
|
44
|
+
constructor({ rules: rules2, required, value, id }) {
|
|
45
|
+
this.rules = rules2;
|
|
46
|
+
this.required = required;
|
|
47
|
+
this.value = value;
|
|
48
|
+
this.id = id;
|
|
49
|
+
}
|
|
50
|
+
validate() {
|
|
51
|
+
let isValid = true;
|
|
52
|
+
let message = "";
|
|
53
|
+
const { value, required, id } = this;
|
|
54
|
+
let result = { success: true, data: value };
|
|
55
|
+
const isEmptyValue = !value && Number.parseFloat(value) !== 0;
|
|
56
|
+
const rules2 = Array.isArray(this.rules) ? this.rules : [this.rules];
|
|
57
|
+
if (!rules2.length || isEmptyValue && required === false) {
|
|
58
|
+
return {
|
|
59
|
+
isValid,
|
|
60
|
+
message,
|
|
61
|
+
id,
|
|
62
|
+
result: { success: true, data: value }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
for (const ruleItem of rules2) {
|
|
66
|
+
if (isValid && ruleItem && "safeParse" in ruleItem) {
|
|
67
|
+
result = ruleItem.safeParse(value);
|
|
68
|
+
isValid = result.success;
|
|
69
|
+
if (!isValid && result.error) {
|
|
70
|
+
message = result.error.issues[0]?.message || "Validation error";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { isValid, message, id, result };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var Validator = class {
|
|
78
|
+
fields;
|
|
79
|
+
params;
|
|
80
|
+
constructor(params) {
|
|
81
|
+
this.params = params || null;
|
|
82
|
+
this.fields = [];
|
|
83
|
+
}
|
|
84
|
+
addField(params) {
|
|
85
|
+
const field = new Field(params);
|
|
86
|
+
this.fields.push(field);
|
|
87
|
+
return field;
|
|
88
|
+
}
|
|
89
|
+
removeField(field) {
|
|
90
|
+
const index = this.fields.indexOf(field);
|
|
91
|
+
if (index > -1) this.fields.splice(index, 1);
|
|
92
|
+
}
|
|
93
|
+
getField(id) {
|
|
94
|
+
return this.fields.find((field) => field.id === id) || null;
|
|
95
|
+
}
|
|
96
|
+
validate() {
|
|
97
|
+
let prevResult;
|
|
98
|
+
const statuses = this.fields.map((field) => {
|
|
99
|
+
if (this.params?.stopAtFirstError && prevResult && prevResult.isValid === false) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
prevResult = field.validate();
|
|
103
|
+
return prevResult;
|
|
104
|
+
});
|
|
105
|
+
const results = statuses.filter((inst) => inst && inst.isValid === false);
|
|
106
|
+
if (results.length) {
|
|
107
|
+
return results[0];
|
|
108
|
+
}
|
|
109
|
+
return { isValid: true, message: "", result: results[0]?.result };
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/use-validator.ts
|
|
114
|
+
function useValidator(value, rules2) {
|
|
115
|
+
const validator = new Validator();
|
|
116
|
+
validator.addField({ value, rules: rules2 });
|
|
117
|
+
const { isValid, ...validateObject } = validator.validate();
|
|
118
|
+
return [isValid, validateObject];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/validator-field.tsx
|
|
122
|
+
var import_react2 = require("react");
|
|
123
|
+
|
|
124
|
+
// src/context.ts
|
|
125
|
+
var import_react = require("react");
|
|
126
|
+
var Context = (0, import_react.createContext)(null);
|
|
127
|
+
|
|
128
|
+
// src/validator-field.tsx
|
|
129
|
+
var ValidatorField = (0, import_react2.forwardRef)(function ValidatorField2(props, _ref) {
|
|
130
|
+
const { children, value } = props;
|
|
131
|
+
const { registerField, unregisterField } = (0, import_react2.useContext)(Context);
|
|
132
|
+
const propsRef = (0, import_react2.useRef)(props);
|
|
133
|
+
propsRef.current = props;
|
|
134
|
+
const handleRef = (0, import_react2.useRef)(null);
|
|
135
|
+
if (!handleRef.current) {
|
|
136
|
+
handleRef.current = {
|
|
137
|
+
get props() {
|
|
138
|
+
return propsRef.current;
|
|
139
|
+
},
|
|
140
|
+
validate: () => {
|
|
141
|
+
const curr = propsRef.current;
|
|
142
|
+
const field = new Field({
|
|
143
|
+
rules: curr.rules,
|
|
144
|
+
required: curr.required,
|
|
145
|
+
value: curr.value,
|
|
146
|
+
id: curr.id
|
|
147
|
+
});
|
|
148
|
+
return field.validate();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
(0, import_react2.useEffect)(() => {
|
|
153
|
+
registerField(handleRef.current);
|
|
154
|
+
return () => {
|
|
155
|
+
unregisterField(handleRef.current);
|
|
156
|
+
};
|
|
157
|
+
}, [registerField, unregisterField]);
|
|
158
|
+
const validity = handleRef.current.validate();
|
|
159
|
+
return typeof children === "function" ? children(validity, value) : children;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/validator-wrapper.tsx
|
|
163
|
+
var import_react3 = require("react");
|
|
164
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
165
|
+
var ValidatorWrapper = (0, import_react3.forwardRef)(function ValidatorWrapper2({ children, stopAtFirstError }, ref) {
|
|
166
|
+
const fieldsRef = (0, import_react3.useRef)([]);
|
|
167
|
+
const registerField = (0, import_react3.useCallback)((field) => {
|
|
168
|
+
if (field && !fieldsRef.current.includes(field)) {
|
|
169
|
+
fieldsRef.current.push(field);
|
|
170
|
+
}
|
|
171
|
+
}, []);
|
|
172
|
+
const unregisterField = (0, import_react3.useCallback)((field) => {
|
|
173
|
+
const index = fieldsRef.current.indexOf(field);
|
|
174
|
+
if (index > -1) fieldsRef.current.splice(index, 1);
|
|
175
|
+
}, []);
|
|
176
|
+
const getField = (0, import_react3.useCallback)((id) => {
|
|
177
|
+
return fieldsRef.current.find((field) => field?.props?.id === id) || null;
|
|
178
|
+
}, []);
|
|
179
|
+
const validate = (0, import_react3.useCallback)(() => {
|
|
180
|
+
const validator = new Validator({ stopAtFirstError });
|
|
181
|
+
for (const comp of fieldsRef.current) {
|
|
182
|
+
validator.addField(comp.props);
|
|
183
|
+
}
|
|
184
|
+
return validator.validate();
|
|
185
|
+
}, [stopAtFirstError]);
|
|
186
|
+
(0, import_react3.useImperativeHandle)(
|
|
187
|
+
ref,
|
|
188
|
+
() => ({
|
|
189
|
+
validate,
|
|
190
|
+
getField,
|
|
191
|
+
registerField,
|
|
192
|
+
unregisterField
|
|
193
|
+
}),
|
|
194
|
+
[validate, getField, registerField, unregisterField]
|
|
195
|
+
);
|
|
196
|
+
const contextValue = (0, import_react3.useMemo)(() => ({ registerField, unregisterField }), [registerField, unregisterField]);
|
|
197
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: contextValue, children });
|
|
198
|
+
});
|
|
199
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
200
|
+
0 && (module.exports = {
|
|
201
|
+
Validator,
|
|
202
|
+
ValidatorField,
|
|
203
|
+
ValidatorWrapper,
|
|
204
|
+
rules,
|
|
205
|
+
useValidator
|
|
206
|
+
});
|
package/example/example.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// biome-ignore lint/correctness/noUnusedImports: <need>
|
|
1
2
|
import React, { createRef, useState } from 'react'
|
|
2
3
|
import ReactDOM from 'react-dom/client'
|
|
3
|
-
import { rules,
|
|
4
|
+
import { rules, ValidatorField, ValidatorWrapper } from '../dist/index'
|
|
4
5
|
|
|
5
6
|
function App() {
|
|
6
7
|
const [email, setEmail] = useState('')
|
|
@@ -15,32 +16,6 @@ function App() {
|
|
|
15
16
|
console.log('success')
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function handleValidateCustomFields() {
|
|
19
|
-
const validator = new Validator({ stopAtFirstError: true })
|
|
20
|
-
const fieldPassword = validator.addField({
|
|
21
|
-
rules: rules.password,
|
|
22
|
-
value: '',
|
|
23
|
-
id: 'for-remove',
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const fieldSearchPassword = validator.getField('for-remove')
|
|
27
|
-
console.log(fieldPassword === fieldSearchPassword)
|
|
28
|
-
|
|
29
|
-
validator.removeField(fieldPassword)
|
|
30
|
-
|
|
31
|
-
validator.addField({
|
|
32
|
-
rules: rules.password,
|
|
33
|
-
value: 'testpassword',
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const { isValid, message, errors } = validator.validate()
|
|
37
|
-
if (!isValid) {
|
|
38
|
-
console.log(isValid, message, errors)
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
console.log('success')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
19
|
return (
|
|
45
20
|
<ValidatorWrapper ref={jsxValidator}>
|
|
46
21
|
<ValidatorField rules={rules.email} value={email}>
|
|
@@ -55,9 +30,6 @@ function App() {
|
|
|
55
30
|
</>
|
|
56
31
|
)}
|
|
57
32
|
</ValidatorField>
|
|
58
|
-
<button onClick={handleValidateCustomFields} type="button">
|
|
59
|
-
Validate custom fields
|
|
60
|
-
</button>
|
|
61
33
|
</ValidatorWrapper>
|
|
62
34
|
)
|
|
63
35
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coxy/react-validator",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🚀 Simple validation form for React or NodeJS apps. useValidator are included ;)",
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "🚀 Simple validation form for React or NodeJS apps with zod. useValidator are included ;)",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"author": "dsshard",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"start": "tsup ./src/index.ts --watch",
|
|
25
|
-
"build": "tsup",
|
|
26
|
-
"lint": "biome lint --fix",
|
|
25
|
+
"build": "tsup ./src/index.ts",
|
|
26
|
+
"lint": "npx biome lint --fix",
|
|
27
27
|
"example": "NODE_ENV=production npx esbuild example/example.tsx --bundle --outfile=example/dist/example.js --minify --format=esm --target=es2020",
|
|
28
28
|
"coverage": "npx codecov",
|
|
29
29
|
"coveralls": "npx coveralls .",
|
|
@@ -33,11 +33,10 @@
|
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=18.x.x"
|
|
35
35
|
},
|
|
36
|
-
"dependencies": {
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
38
|
+
},
|
|
37
39
|
"devDependencies": {
|
|
38
|
-
"react": ">=18.x.x",
|
|
39
|
-
"react-dom": ">=18.x.x",
|
|
40
|
-
"@biomejs/biome": "2.2.2",
|
|
41
40
|
"@testing-library/dom": "10.4.1",
|
|
42
41
|
"@testing-library/react": "16.3.0",
|
|
43
42
|
"@types/jest": "30.0.0",
|
|
@@ -46,10 +45,12 @@
|
|
|
46
45
|
"@types/react-dom": "19.1.7",
|
|
47
46
|
"jest": "30.0.5",
|
|
48
47
|
"jest-environment-jsdom": "30.0.5",
|
|
49
|
-
"
|
|
48
|
+
"react": ">=18.x.x",
|
|
49
|
+
"react-dom": ">=18.x.x",
|
|
50
50
|
"react-test-renderer": "19.1.1",
|
|
51
51
|
"ts-jest": "29.4.0",
|
|
52
52
|
"ts-node": "10.9.2",
|
|
53
|
+
"tsup": "8.5.0",
|
|
53
54
|
"typescript": "5.8.3"
|
|
54
55
|
}
|
|
55
56
|
}
|
package/src/context.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { rules } from './rules'
|
|
2
2
|
|
|
3
|
-
export type { ErrorMessage, FieldParams,
|
|
3
|
+
export type { ErrorMessage, FieldParams, Validity } from './types'
|
|
4
4
|
export { useValidator } from './use-validator'
|
|
5
5
|
export { Validator } from './validator'
|
|
6
6
|
export { ValidatorField } from './validator-field'
|
package/src/rules.test.ts
CHANGED
|
@@ -1,132 +1,37 @@
|
|
|
1
|
+
import type { ZodSafeParseResult } from 'zod'
|
|
1
2
|
import { rules } from './rules'
|
|
2
3
|
|
|
3
4
|
it('check rule email', () => {
|
|
4
5
|
expect(rules.email.length).toBe(2)
|
|
5
6
|
|
|
6
|
-
let result:
|
|
7
|
+
let result: ZodSafeParseResult<string>
|
|
7
8
|
// email.length > 0
|
|
8
|
-
result = rules.email[0].
|
|
9
|
-
expect(result).toBe(true)
|
|
9
|
+
result = rules.email[0].safeParse('test')
|
|
10
|
+
expect(result.success).toBe(true)
|
|
10
11
|
|
|
11
12
|
// email check regexp
|
|
12
|
-
result = rules.email[1].
|
|
13
|
-
expect(result).toBe(false)
|
|
13
|
+
result = rules.email[1].safeParse('test')
|
|
14
|
+
expect(result.success).toBe(false)
|
|
14
15
|
|
|
15
16
|
// email check regexp
|
|
16
|
-
result = rules.email[1].
|
|
17
|
-
expect(result).toBe(true)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('check rule password', () => {
|
|
21
|
-
expect(rules.password.length).toBe(2)
|
|
22
|
-
|
|
23
|
-
let result: boolean
|
|
24
|
-
// password.length > 0
|
|
25
|
-
result = rules.password[0].rule('test')
|
|
26
|
-
expect(result).toBe(true)
|
|
27
|
-
|
|
28
|
-
// password.length > 5
|
|
29
|
-
result = rules.password[1].rule('test')
|
|
30
|
-
expect(result).toBe(false)
|
|
17
|
+
result = rules.email[1].safeParse('test@gmail.com')
|
|
18
|
+
expect(result.success).toBe(true)
|
|
31
19
|
})
|
|
32
20
|
|
|
33
21
|
it('check rule bool', () => {
|
|
34
|
-
expect(rules.
|
|
22
|
+
expect(rules.isTrue.length).toBe(1)
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(result).toBe(true)
|
|
24
|
+
const result = rules.isTrue[0].safeParse(true)
|
|
25
|
+
expect(result.success).toBe(true)
|
|
39
26
|
})
|
|
40
27
|
|
|
41
28
|
it('check rule notEmpty', () => {
|
|
42
29
|
expect(rules.notEmpty.length).toBe(1)
|
|
43
30
|
|
|
44
|
-
let result:
|
|
45
|
-
result = rules.notEmpty[0].
|
|
46
|
-
expect(result).toBe(false)
|
|
47
|
-
|
|
48
|
-
result = rules.notEmpty[0].rule('test')
|
|
49
|
-
expect(result).toBe(true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('check rule min', () => {
|
|
53
|
-
expect(rules.min(1).length).toBe(1)
|
|
54
|
-
expect(typeof rules.min).toBe('function')
|
|
55
|
-
|
|
56
|
-
let result: boolean
|
|
57
|
-
result = rules.min(10)[0].rule('')
|
|
58
|
-
expect(result).toBe(false)
|
|
59
|
-
|
|
60
|
-
result = rules.min(9)[0].rule('test-test-test')
|
|
61
|
-
expect(result).toBe(false)
|
|
62
|
-
|
|
63
|
-
result = rules.min(9)[0].rule('11')
|
|
64
|
-
expect(result).toBe(true)
|
|
65
|
-
|
|
66
|
-
// @ts-expect-error
|
|
67
|
-
result = rules.min(9)[0].rule(10)
|
|
68
|
-
expect(result).toBe(true)
|
|
69
|
-
|
|
70
|
-
result = rules.min(9)[0].rule('8')
|
|
71
|
-
expect(result).toBe(false)
|
|
72
|
-
|
|
73
|
-
// @ts-expect-error
|
|
74
|
-
result = rules.min(9)[0].rule(7)
|
|
75
|
-
expect(result).toBe(false)
|
|
76
|
-
|
|
77
|
-
result = rules.min(-1)[0].rule('')
|
|
78
|
-
expect(result).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('check rule max', () => {
|
|
82
|
-
let result: boolean
|
|
83
|
-
result = rules.max(10)[0].rule('')
|
|
84
|
-
expect(result).toBe(false)
|
|
85
|
-
|
|
86
|
-
result = rules.max(9)[0].rule('test-test-test')
|
|
87
|
-
expect(result).toBe(false)
|
|
88
|
-
|
|
89
|
-
result = rules.max(9)[0].rule('11')
|
|
90
|
-
expect(result).toBe(false)
|
|
91
|
-
|
|
92
|
-
// @ts-expect-error
|
|
93
|
-
result = rules.max(9)[0].rule(10)
|
|
94
|
-
expect(result).toBe(false)
|
|
95
|
-
|
|
96
|
-
result = rules.max(9)[0].rule('5')
|
|
97
|
-
expect(result).toBe(true)
|
|
98
|
-
|
|
99
|
-
// @ts-expect-error
|
|
100
|
-
result = rules.max(9)[0].rule(5)
|
|
101
|
-
expect(result).toBe(true)
|
|
102
|
-
|
|
103
|
-
result = rules.max(-1)[0].rule('')
|
|
104
|
-
expect(result).toBe(false)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('check rule length', () => {
|
|
108
|
-
let result: boolean
|
|
109
|
-
result = rules.length(1)[0].rule('')
|
|
110
|
-
expect(result).toBe(false)
|
|
111
|
-
|
|
112
|
-
result = rules.length(1)[0].rule('1')
|
|
113
|
-
expect(result).toBe(true)
|
|
114
|
-
|
|
115
|
-
result = rules.length(1, 10)[0].rule('test-test-test')
|
|
116
|
-
expect(result).toBe(true)
|
|
117
|
-
|
|
118
|
-
result = rules.length(1, 10)[1].rule('test-test-test')
|
|
119
|
-
expect(result).toBe(false)
|
|
120
|
-
|
|
121
|
-
result = rules.length(1, 10)[0].rule('lol')
|
|
122
|
-
expect(result).toBe(true)
|
|
123
|
-
|
|
124
|
-
result = rules.length(1, 10)[1].rule('lol')
|
|
125
|
-
expect(result).toBe(true)
|
|
126
|
-
|
|
127
|
-
result = rules.length(1)[1].rule('test undefined 2 param')
|
|
128
|
-
expect(result).toBe(true)
|
|
31
|
+
let result: ZodSafeParseResult<string>
|
|
32
|
+
result = rules.notEmpty[0].safeParse('')
|
|
33
|
+
expect(result.success).toBe(false)
|
|
129
34
|
|
|
130
|
-
result = rules.
|
|
131
|
-
expect(result).toBe(
|
|
35
|
+
result = rules.notEmpty[0].safeParse('test')
|
|
36
|
+
expect(result.success).toBe(true)
|
|
132
37
|
})
|
package/src/rules.ts
CHANGED
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from 'zod'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const emailReg =
|
|
5
|
-
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
|
6
|
-
|
|
7
|
-
export type ValidatorRules = ValidatorRule[]
|
|
3
|
+
export type ValidatorRules = z.ZodType[] | z.ZodType
|
|
8
4
|
|
|
9
5
|
export const rules = {
|
|
10
|
-
notEmpty: [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
message: 'Value is required',
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
|
|
17
|
-
bool: [
|
|
18
|
-
{
|
|
19
|
-
rule: (value: string) => !!value,
|
|
20
|
-
message: 'Value is required',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
|
|
24
|
-
password: [
|
|
25
|
-
{
|
|
26
|
-
rule: (value: string) => value.length > 0,
|
|
27
|
-
message: 'Password field cannot be empty',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
rule: (value: string) => value.length > 5,
|
|
31
|
-
message: 'Password field can not be less than 6 characters',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
|
|
35
|
-
email: [
|
|
36
|
-
{
|
|
37
|
-
rule: (value: string) => !!value && value !== '' && value.length !== 0,
|
|
38
|
-
message: 'Email is required',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
rule: (value: string) => emailReg.test(String(value).toLowerCase()),
|
|
42
|
-
message: 'Email is invalid',
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
|
|
46
|
-
min: (min: number) => [
|
|
47
|
-
{
|
|
48
|
-
rule: (value: string) => Number.parseFloat(value) > min,
|
|
49
|
-
message: `The value must be greater than ${min}`,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
|
|
53
|
-
max: (max: number) => [
|
|
54
|
-
{
|
|
55
|
-
rule: (value: string) => Number.parseFloat(value) < max,
|
|
56
|
-
message: `The value must be smaller ${max}`,
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
|
|
60
|
-
length: (min: number, max?: number) => [
|
|
61
|
-
{
|
|
62
|
-
rule: (value: string) => String(value).length >= min,
|
|
63
|
-
message: `No less than ${min} symbols`,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
rule: (value: string) => (max !== undefined ? String(value).length <= max : true),
|
|
67
|
-
message: `No more than ${max} symbols`,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
6
|
+
notEmpty: [z.string().min(1, { error: 'Field is required' })],
|
|
7
|
+
isTrue: [z.boolean({ error: 'Value is required' }).and(z.literal(true))],
|
|
8
|
+
email: [z.string().min(1, { error: 'Email is required' }), z.email({ message: 'Email is invalid' })],
|
|
70
9
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
+
import type { ZodSafeParseResult } from 'zod/index'
|
|
1
2
|
import type { ValidatorRules } from './rules'
|
|
2
3
|
import type { Value } from './validator-field'
|
|
3
4
|
|
|
4
|
-
type Fn = (value: Value) => string
|
|
5
|
-
|
|
6
|
-
export interface ValidatorRule {
|
|
7
|
-
rule: (value: Value) => boolean
|
|
8
|
-
message: string | Fn
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
export interface ErrorMessage {
|
|
12
6
|
message: string
|
|
13
7
|
isValid: boolean
|
|
@@ -16,7 +10,7 @@ export interface ErrorMessage {
|
|
|
16
10
|
export interface Validity {
|
|
17
11
|
message: string
|
|
18
12
|
isValid: boolean
|
|
19
|
-
|
|
13
|
+
result: ZodSafeParseResult<unknown>
|
|
20
14
|
id?: string | number
|
|
21
15
|
}
|
|
22
16
|
|
|
@@ -24,14 +24,14 @@ jest.useFakeTimers()
|
|
|
24
24
|
it('check state change and hide field', () => {
|
|
25
25
|
function Comp() {
|
|
26
26
|
const [value, setValue] = useState(false)
|
|
27
|
-
const [isValid, validateObject] = useValidator(value, rules.
|
|
27
|
+
const [isValid, validateObject] = useValidator(value, rules.isTrue)
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
setTimeout(() => {
|
|
31
31
|
act(() => {
|
|
32
32
|
setValue(true)
|
|
33
33
|
})
|
|
34
|
-
},
|
|
34
|
+
}, 200)
|
|
35
35
|
}, [])
|
|
36
36
|
|
|
37
37
|
return (
|
|
@@ -46,7 +46,7 @@ it('check state change and hide field', () => {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
expect(screen.getByTestId('test1').textContent).toContain('false')
|
|
49
|
-
expect(screen.getByTestId('test2').textContent).toContain('
|
|
49
|
+
expect(screen.getByTestId('test2').textContent).toContain('Invalid input: expected true')
|
|
50
50
|
|
|
51
51
|
jest.runAllTimers()
|
|
52
52
|
|