vest 4.0.0-dev-366a8b → 4.0.0-dev-e266d9
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/CHANGELOG.md +70 -49
- package/README.md +2 -112
- package/dist/cjs/classnames.development.js +3 -3
- package/dist/cjs/classnames.production.js +1 -1
- package/dist/cjs/compose.js +7 -0
- package/dist/cjs/compounds.js +7 -0
- package/dist/cjs/enforce/compose.development.js +139 -0
- package/dist/cjs/enforce/compose.production.js +1 -0
- package/dist/cjs/enforce/compounds.development.js +132 -0
- package/dist/cjs/enforce/compounds.production.js +1 -0
- package/dist/cjs/enforce/package.json +1 -0
- package/dist/cjs/enforce/schema.development.js +144 -0
- package/dist/cjs/enforce/schema.production.js +1 -0
- package/dist/cjs/promisify.development.js +1 -1
- package/dist/cjs/promisify.production.js +1 -1
- package/dist/cjs/schema.js +7 -0
- package/dist/cjs/vest.development.js +608 -1097
- package/dist/cjs/vest.production.js +1 -1
- package/dist/es/classnames.development.js +3 -3
- package/dist/es/classnames.production.js +1 -1
- package/dist/es/enforce/compose.development.js +137 -0
- package/dist/es/enforce/compose.production.js +1 -0
- package/dist/es/enforce/compounds.development.js +130 -0
- package/dist/es/enforce/compounds.production.js +1 -0
- package/dist/es/enforce/package.json +1 -0
- package/dist/es/enforce/schema.development.js +140 -0
- package/dist/es/enforce/schema.production.js +1 -0
- package/dist/es/promisify.development.js +1 -1
- package/dist/es/promisify.production.js +1 -1
- package/dist/es/vest.development.js +602 -1097
- package/dist/es/vest.production.js +1 -1
- package/dist/umd/classnames.development.js +3 -3
- package/dist/umd/classnames.production.js +1 -1
- package/dist/umd/enforce/compose.development.js +143 -0
- package/dist/umd/enforce/compose.production.js +1 -0
- package/dist/umd/enforce/compounds.development.js +136 -0
- package/dist/umd/enforce/compounds.production.js +1 -0
- package/dist/umd/enforce/schema.development.js +148 -0
- package/dist/umd/enforce/schema.production.js +1 -0
- package/dist/umd/promisify.development.js +1 -1
- package/dist/umd/promisify.production.js +1 -1
- package/dist/umd/vest.development.js +1693 -2185
- package/dist/umd/vest.production.js +1 -1
- package/enforce/compose/package.json +7 -0
- package/enforce/compounds/package.json +7 -0
- package/enforce/schema/package.json +7 -0
- package/package.json +107 -13
- package/testUtils/mockThrowError.ts +16 -0
- package/types/classnames.d.ts +2 -2
- package/types/enforce/compose.d.ts +134 -0
- package/types/enforce/compounds.d.ts +146 -0
- package/types/enforce/schema.d.ts +151 -0
- package/types/vest.d.ts +31 -196
- package/docs/.nojekyll +0 -0
- package/docs/README.md +0 -113
- package/docs/_assets/favicon.ico +0 -0
- package/docs/_assets/vest-logo.png +0 -0
- package/docs/_sidebar.md +0 -14
- package/docs/cross_field_validations.md +0 -33
- package/docs/enforce.md +0 -11
- package/docs/exclusion.md +0 -129
- package/docs/getting_started.md +0 -72
- package/docs/group.md +0 -142
- package/docs/index.html +0 -41
- package/docs/migration.md +0 -202
- package/docs/n4s/rules.md +0 -1282
- package/docs/node.md +0 -36
- package/docs/optional.md +0 -103
- package/docs/result.md +0 -249
- package/docs/state.md +0 -102
- package/docs/test.md +0 -172
- package/docs/utilities.md +0 -109
- package/docs/warn.md +0 -82
package/docs/node.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Using Vest in node
|
|
2
|
-
|
|
3
|
-
Using Vest in node is mostly the same as it is in the browser, but you should consider your runtime.
|
|
4
|
-
|
|
5
|
-
## Validation state
|
|
6
|
-
|
|
7
|
-
When running your validations in your api, you usually want to have stateless validations to prevent leakage between requests.
|
|
8
|
-
|
|
9
|
-
Read more about [Vest's state](./state).
|
|
10
|
-
|
|
11
|
-
## require vs import
|
|
12
|
-
|
|
13
|
-
Depending on your node version and the module system you support you can use different syntax to include Vest.
|
|
14
|
-
|
|
15
|
-
### Most compatible: commonjs
|
|
16
|
-
|
|
17
|
-
To be on the safe side and compatible with all node versions, use a `require` statement.
|
|
18
|
-
|
|
19
|
-
```js
|
|
20
|
-
const vest = require('vest');
|
|
21
|
-
const { test, enforce } = vest;
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Node 14
|
|
25
|
-
|
|
26
|
-
With node 14's support of [package entry points](https://nodejs.org/api/esm.html#esm_package_entry_points), node should be able to detect on its own which import style you use and load the correct bundle.
|
|
27
|
-
|
|
28
|
-
Both of the following should work:
|
|
29
|
-
|
|
30
|
-
```js
|
|
31
|
-
import { create, test } from 'vest';
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
const vest = require('vest');
|
|
36
|
-
```
|
package/docs/optional.md
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# optional fields
|
|
2
|
-
|
|
3
|
-
By default, all the tests inside Vest are required in order for the suite to be considered as "valid". Sometimes your app's logic may allow tests not to be filled out and you want them not to be accounted for in the suites validity.
|
|
4
|
-
|
|
5
|
-
For cases like this, Vest provides the `optional` function which allows you to mark a a field, or multiple fields as optional. Vest's definition of "optional" is that the field did not have any test runs in the lifetime of the suite.
|
|
6
|
-
|
|
7
|
-
If your app requires a more custom logic, please see the [advanced section below](#advanced).
|
|
8
|
-
|
|
9
|
-
## Basic Usage - allowing tests not to run
|
|
10
|
-
|
|
11
|
-
`optional` can take a field name as its argument, or an array of field names.
|
|
12
|
-
|
|
13
|
-
```js
|
|
14
|
-
import { create, optional, only, test, enforce } from 'vest';
|
|
15
|
-
|
|
16
|
-
const suite = create((data, currentField) => {
|
|
17
|
-
only(currentField); // only validate this specified field
|
|
18
|
-
|
|
19
|
-
optional(['pet_color', 'pet_age']);
|
|
20
|
-
/** Equivalent to:
|
|
21
|
-
* optional('pet_color')
|
|
22
|
-
* optional('pet_age')
|
|
23
|
-
**/
|
|
24
|
-
|
|
25
|
-
test('pet_name', 'Pet Name is required', () => {
|
|
26
|
-
enforce(data.pet_name).isNotEmpty();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('pet_color', 'If provided, pet color must be a string', () => {
|
|
30
|
-
enforce(data.color).isString();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('pet_age', 'If provided, pet age must be numeric', () => {
|
|
34
|
-
enforce(data.age).isNumeric();
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
suite({ name: 'Indie' }, /* -> only validate pet_name */ 'pet_name').isValid();
|
|
39
|
-
// ✅ Since pet_color and pet_age are optional, the suite may still be valid
|
|
40
|
-
|
|
41
|
-
suite({ age: 'Five' }, /* -> only validate pet_age */ 'pet_age').isValid();
|
|
42
|
-
// 🚨 When erroring, optional fields still make the suite invalid
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Advanced Usage - Supplying custom omission function :id=advanced
|
|
46
|
-
|
|
47
|
-
Since every app is different, your app's logic may require some other definition of "optional", for example - if the user typed inside the field and then removed its content, or alternatively - if a field may be empty only if a different field is supplied - then Vest cannot be aware of this logic, and you will have to tell Vest to conditionally omit the results for this field by supplying `optional` with a custom omission function.
|
|
48
|
-
|
|
49
|
-
To provide a custom optional function, instead of passing a list of fields, you need to provide an object with predicate functions. These functions will be run when your suite finishes its **synchronous** run, and when they evaluate to true - will omit _any_ failures your field might have from the suite.
|
|
50
|
-
|
|
51
|
-
!> **IMPORTANT** You should avoid using the custom omission function along with async tests. This is unsupported and may cause unexpected behavior. The reason for this limitation is due to the fact that the omission conditionals are calculated at the end of the suite, while the async tests may keep running afterwards. Allowing it will require re-calculation for each async test that finishes, which could be expensive.
|
|
52
|
-
|
|
53
|
-
### Examples
|
|
54
|
-
|
|
55
|
-
**An example allowing a field to be empty even if its `touched` or `dirty`**
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
const suite = create(data => {
|
|
59
|
-
optional({
|
|
60
|
-
pet_name: () => !data.pet_name,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('pet_name', 'Pet Name may be left empty', () => {
|
|
64
|
-
enforce(data.pet_name).isNotEmpty();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**An example allowing a field to be empty if a different field is filled**
|
|
70
|
-
|
|
71
|
-
```js
|
|
72
|
-
const suite = create(data => {
|
|
73
|
-
optional({
|
|
74
|
-
pet_name: () => !suite.get().hasErrors('owner_name'),
|
|
75
|
-
owner_name: () => !suite.get().hasErrors('pet_name'),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test(
|
|
79
|
-
'pet_name',
|
|
80
|
-
'Pet Name may be left empty only if owner name is supplied',
|
|
81
|
-
() => {
|
|
82
|
-
enforce(data.pet_name).isNotEmpty();
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
test(
|
|
87
|
-
'owner_name',
|
|
88
|
-
'Owner Name may be left empty only if pet name is supplied',
|
|
89
|
-
() => {
|
|
90
|
-
enforce(data.owner_name).isNotEmpty();
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Difference between `optional` and `warn`
|
|
97
|
-
|
|
98
|
-
While on its surface, optional might seem similar to warn, they are quite different.
|
|
99
|
-
optional, like "only" and "skip" is set on the field level, which means that when set - all tests of an optional field are considered optional. Warn, on the other hand - is set on the test level, so the only tests affected are the tests that have the "warn" option applied within them.
|
|
100
|
-
|
|
101
|
-
Another distinction is that warning tests cannot set the suite to be invalid.
|
|
102
|
-
|
|
103
|
-
There may be rare occasions in which you have an optional and a warning only field, in which case, you may combine the two.
|
package/docs/result.md
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# Vest's result object
|
|
2
|
-
|
|
3
|
-
Vest validations return a results object that holds all the information regarding the current run, and methods to easily interact with the data.
|
|
4
|
-
|
|
5
|
-
A result object would look somewhat like this:
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
{
|
|
9
|
-
'errorCount': Number 0, // Overall count of errors in the suite
|
|
10
|
-
'warnCount': Number 0, // Overall count of warnings in the suite
|
|
11
|
-
'testCount': Number 0, // Overall test count for the suite (passing, failing and warning)
|
|
12
|
-
'tests': Object { // An object containing all non-skipped tests
|
|
13
|
-
'fieldName': Object { // Name of each field
|
|
14
|
-
'errorCount': Number 0, // Error count per field
|
|
15
|
-
'errors': Array [], // Array of error messages fer field (may be undefined)
|
|
16
|
-
'warnings': Array [], // Array of warning messages fer field (may be undefined)
|
|
17
|
-
'warnCount': Number 0, // Warning count per field
|
|
18
|
-
'testCount': Number 0, // Overall test count for the field (passing, failing and warning)
|
|
19
|
-
},
|
|
20
|
-
'groups': Object { // An object containing groups declared in the suite
|
|
21
|
-
'fieldName': Object { // Subset of res.tests[fieldName] only containing tests
|
|
22
|
-
/*... */ // only containing tests that ran within the group
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Accessing the recent result object with `.get`
|
|
30
|
-
|
|
31
|
-
If you need to access your validation results out of context - for example, from a different UI component or function, you can use `.get()` - a function that exists as a property of your validation suite.
|
|
32
|
-
|
|
33
|
-
In case your validations did not run yet, `.get` returns an empty validation result object - which can be helpful when trying to access validation result object when rendering the initial UI, or setting it in the initial state of your components.
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
const suite = create(() => {
|
|
37
|
-
/*...*/
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
suite.get(); // -> returns the most recent result object for the current suite
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
# Result Object Methods:
|
|
44
|
-
|
|
45
|
-
Along with these values, the result object exposes the following methods:
|
|
46
|
-
|
|
47
|
-
## `isValid` function
|
|
48
|
-
|
|
49
|
-
`isValid` returns whether the validation suite as a whole or a single field is valid or not.
|
|
50
|
-
|
|
51
|
-
A _suite_ is considered valid if the following conditions are met:
|
|
52
|
-
|
|
53
|
-
- There are no errors (`hasErrors() === false`) in the suite - warnings are not counted as errors.
|
|
54
|
-
- All non [optional](./optional) fields have passing tests.
|
|
55
|
-
- There are no pending async tests.
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
result.isValid();
|
|
59
|
-
|
|
60
|
-
suite.get().isValid();
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
A _field_ is considered valid if the following conditions are met:
|
|
64
|
-
|
|
65
|
-
- The field has no errors (`hasErrors() === false`) or the field is omitted via the functional "optional" API.
|
|
66
|
-
- All non optional tests for the field are passing.
|
|
67
|
-
- The field has no pending tests.
|
|
68
|
-
|
|
69
|
-
```js
|
|
70
|
-
result.isValid('username');
|
|
71
|
-
|
|
72
|
-
suite.get().isValid('username');
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
?> **Note** when `isValid` equals `false`, it does not necessarily mean that the form is inValid, but that it might not be valid _yet_. For example, if not all the fields are filled, the form is simply not valid, even though it may not be strictly invalid.
|
|
76
|
-
|
|
77
|
-
## `hasErrors` and `hasWarnings` functions
|
|
78
|
-
|
|
79
|
-
If you only need to know if a certain field has validation errors or warnings but don't really care which they are, you can use `hasErrors` or `hasWarnings` functions.
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
resultObject.hasErrors('username');
|
|
83
|
-
// true
|
|
84
|
-
|
|
85
|
-
resultObject.hasWarnings('password');
|
|
86
|
-
// false
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
In case you want to know whether the whole suite has errors or warnings (to prevent submit, for example), you can use the same functions, just without specifying a field
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
resultObject.hasErrors();
|
|
93
|
-
// true
|
|
94
|
-
|
|
95
|
-
resultObject.hasWarnings();
|
|
96
|
-
// true
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## `hasErrorsByGroup` and `hasWarningsByGroup` functions
|
|
100
|
-
|
|
101
|
-
Similar to `hasErrors` and `hasWarnings`, but returns the result for a specified [group](./group)
|
|
102
|
-
|
|
103
|
-
To get the result for a given field in the group:
|
|
104
|
-
|
|
105
|
-
```js
|
|
106
|
-
resultObject.hasErrorsByGroup('groupName', 'fieldName');
|
|
107
|
-
// true
|
|
108
|
-
|
|
109
|
-
resultObject.hasWarningsByGroup('groupName', 'fieldName');
|
|
110
|
-
// false
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
And to get the result for a whole group.
|
|
114
|
-
|
|
115
|
-
```js
|
|
116
|
-
resultObject.hasErrorsByGroup('groupName');
|
|
117
|
-
// true
|
|
118
|
-
|
|
119
|
-
resultObject.hasWarningsByGroup('groupName');
|
|
120
|
-
// true
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
[Read more about groups](./group)
|
|
124
|
-
|
|
125
|
-
## `getErrors` and `getWarnings` functions
|
|
126
|
-
|
|
127
|
-
These functions return an array of errors for the specified field. If no field specified, it returns an object with all fields as keys and their error arrays as values.
|
|
128
|
-
|
|
129
|
-
```js
|
|
130
|
-
resultObject.getErrors('username');
|
|
131
|
-
// ['Username is too short', `Username already exists`]
|
|
132
|
-
|
|
133
|
-
resultObject.getWarnings('password');
|
|
134
|
-
// ['Password must contain special characters']
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
If there are no errors for the field, the function defaults to an empty array:
|
|
138
|
-
|
|
139
|
-
```js
|
|
140
|
-
resultObject.getErrors('username');
|
|
141
|
-
// []
|
|
142
|
-
|
|
143
|
-
resultObject.getWarnings('username');
|
|
144
|
-
// []
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
You can also call these functions without a field name, which will return you an array per field:
|
|
148
|
-
|
|
149
|
-
```js
|
|
150
|
-
resultObject.getErrors();
|
|
151
|
-
|
|
152
|
-
// {
|
|
153
|
-
// username: ['Username is too short', `Username already exists`],
|
|
154
|
-
// password: ['Password must contain special characters']
|
|
155
|
-
// }
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
!> **NOTE** If you did not specify error messages for your tests, your errors array will be empty as well. In such case you should always rely on `.hasErrors()` instead.
|
|
159
|
-
|
|
160
|
-
## `getErrorsByGroup` and `getWarningsByGroup` functions
|
|
161
|
-
|
|
162
|
-
Just like get `getErrors` and `getWarnings`, but narrows the result to a specified [group](./group).
|
|
163
|
-
|
|
164
|
-
```js
|
|
165
|
-
resultObject.getErrorsByGroup('groupName', 'fieldName');
|
|
166
|
-
resultObject.getWarningsByGroup('groupName', 'fieldName');
|
|
167
|
-
resultObject.getErrorsByGroup('groupName'');
|
|
168
|
-
resultObject.getWarningsByGroup('groupName'');
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
[Read more about groups](./group).
|
|
172
|
-
|
|
173
|
-
## `.done()`
|
|
174
|
-
|
|
175
|
-
Done is a function that can be chained to your validation suite, and allows invoking callbacks whenever a specific, or all, tests finish their validation - regardless of the validation result.
|
|
176
|
-
|
|
177
|
-
If we specify a field name in our `done` call, vest will not wait for the whole suite to finish before running our callback. It will invoke immediately when all tests with that given name finished running.
|
|
178
|
-
|
|
179
|
-
`.done()` calls can be infinitely chained after one another, and as the validation suite completes - they will all run immediately.
|
|
180
|
-
|
|
181
|
-
`done` takes one or two arguments:
|
|
182
|
-
|
|
183
|
-
| Name | Type | Optional | Description |
|
|
184
|
-
| ----------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
-
| `fieldName` | `String` | Yes | If passed, the current done call will not wait for the whole suite to complete, but instead wait for a certain field to finish. |
|
|
186
|
-
| `callback` | `Function` | No | A callback to be run when either the whole suite or the specified field finished running. |
|
|
187
|
-
|
|
188
|
-
The result object is being passed down to the `done` object as an argument.
|
|
189
|
-
|
|
190
|
-
**Example**
|
|
191
|
-
|
|
192
|
-
In the below example, the `done` callback for `UserName` may run before the whole suite finishes. Only when the rest of the suite finishes, it will call the other two done callbacks that do not have a field name specified.
|
|
193
|
-
|
|
194
|
-
```js
|
|
195
|
-
import { create, test, enforce } from 'vest';
|
|
196
|
-
|
|
197
|
-
const suite = create(data => {
|
|
198
|
-
test(
|
|
199
|
-
'UserEmail',
|
|
200
|
-
'Marked as spam address',
|
|
201
|
-
async () => await isKnownSpammer(data.address)
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
test(
|
|
205
|
-
'UserName',
|
|
206
|
-
'must not be blacklisted',
|
|
207
|
-
async () => await isBlacklistedUser(data.username)
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const validationResult = suite(data)
|
|
212
|
-
.done('UserName', res => {
|
|
213
|
-
if (res.hasErrors('UserName')) {
|
|
214
|
-
showUserNameErrors(res.errors);
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
.done(output => {
|
|
218
|
-
reportToServer(output);
|
|
219
|
-
})
|
|
220
|
-
.done(output => {
|
|
221
|
-
promptUserQuestionnaire(output);
|
|
222
|
-
});
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
!> **IMPORTANT** .done calls must not be used conditionally - especially when involving async tests. This might cause unexpected behavior or missed callbacks. Instead, if needed, perform your conditional logic within your callback.
|
|
226
|
-
|
|
227
|
-
```js
|
|
228
|
-
// 🚨 This might not work as expected when working with async validations
|
|
229
|
-
|
|
230
|
-
if (field === 'username') {
|
|
231
|
-
result.done(() => {
|
|
232
|
-
/*do something*/
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
```js
|
|
238
|
-
// ✅ Instead, perform your checks within your done callback
|
|
239
|
-
|
|
240
|
-
result.done(() => {
|
|
241
|
-
if (field === 'username') {
|
|
242
|
-
/*do something*/
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Read more on:
|
|
248
|
-
|
|
249
|
-
[Optional tests](./optional)
|
package/docs/state.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# Understanding Vest's state
|
|
2
|
-
|
|
3
|
-
Vest is designed to help perform validations on user inputs. The nature of user inputs is that they are filled one by one by the user. In order to provide good user experience, the best approach is to validate fields as the user type, or when they leave the field.
|
|
4
|
-
|
|
5
|
-
The difficult part when validating upon user interaction is that we want to only validate the field that the user is currently interacting with, and not the rest of the form. This
|
|
6
|
-
This can be done with Vest's [`only()` hook](./exclusion). That's where the state mechanism is becoming useful.
|
|
7
|
-
|
|
8
|
-
When you have skipped fields in your validation suite, vest will try to see if those skipped fields ran in the previous suite, and merge them into the currently running suite result - so the result object you get will include all the fields that your user interacted with.
|
|
9
|
-
|
|
10
|
-
## What Vest's state does
|
|
11
|
-
|
|
12
|
-
- _Skipped field merge_
|
|
13
|
-
|
|
14
|
-
As mentioned before - whenever you skip a field, vest will look for it in your previously ran validations and add it to the current result.
|
|
15
|
-
|
|
16
|
-
- _Lagging async `done` callback blocking_
|
|
17
|
-
|
|
18
|
-
In case you have an async test that didn't finish from the previous suite run - and you already ran another async test for the same field - vest will block the [`done()`]('./result#done) callbacks for that field from running for the previous suite result.
|
|
19
|
-
|
|
20
|
-
# Drawbacks when using stateful validations
|
|
21
|
-
|
|
22
|
-
When the validations are stateful, you get the benefit of not having to know which fields have already been validated, or keeping track of their previous results.
|
|
23
|
-
|
|
24
|
-
The drawback of this approach is that when you run the same form in multiple-unrelated contexts, the previous validation state still holds the previous result.
|
|
25
|
-
|
|
26
|
-
Here are a few examples and their solutions:
|
|
27
|
-
|
|
28
|
-
## Single Page Application - suite result retention
|
|
29
|
-
|
|
30
|
-
This scenario applies for cases when your form is a part of an SPA with client side routing. Let's assume your user successfuly submits the form, navigates outside of the page, and then sometime and then, later in the same session, they navigate back to the form.
|
|
31
|
-
|
|
32
|
-
The form will then have a successful validation state since the previous result is stored in the suite state.
|
|
33
|
-
|
|
34
|
-
### Solution: Resetting suite state with `.reset();`
|
|
35
|
-
|
|
36
|
-
In some cases, such as form reset, you want to discard of previous validation results. This can be done with `vest.reset()`.
|
|
37
|
-
|
|
38
|
-
`.reset` disables all pending async tests in your suite and empties the state out.
|
|
39
|
-
|
|
40
|
-
### Usage:
|
|
41
|
-
|
|
42
|
-
`.rese()` Is a property on your validation suite, calling it will remove your suite's state.
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
import { create } from 'vest';
|
|
46
|
-
|
|
47
|
-
const suite = create(() => {
|
|
48
|
-
// Your tests go here
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
suite.reset(); // validation result is removed from Vest's state.
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Dynamically added fields
|
|
55
|
-
|
|
56
|
-
When your form contains dynamically added fields, for example - when a customer can add fields to their checkout form on the fly, those items would still exist in the suite state when the user removed them from the form. This means that you may have an unsuccessful suite result, even though it should be successful.
|
|
57
|
-
|
|
58
|
-
### Solution: Removing a single field from the validation result
|
|
59
|
-
|
|
60
|
-
Instead of resetting the whole suite, you can alternatively remove just one field. This is useful when dynamically adding and removing fields upon user interaction - and you want to delete a deleted field from the state.
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
import { create, test } from 'vest';
|
|
64
|
-
|
|
65
|
-
const suite = create(() => {
|
|
66
|
-
// Your tests go here
|
|
67
|
-
|
|
68
|
-
test('username', 'must be at least 3 chars long', () => {
|
|
69
|
-
/*...*/
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
suite.remove('username'); // validation result is removed from Vest's state.
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Server side validations
|
|
77
|
-
|
|
78
|
-
When running your validations on the server, you want to keep each request isolated with its own state, and not update the same validation state between requests. Doing that can cause failed validations to seem successful or vice versa due to different requests relying on the same state.
|
|
79
|
-
|
|
80
|
-
### Solution: Treat validations as stateless
|
|
81
|
-
|
|
82
|
-
While when on the browser you usually want to treat validations as statefull - even though it might sometimes not be the case - on the server you almost always want to treat your validations as stateless.
|
|
83
|
-
|
|
84
|
-
To do that, all you need to do is wrap your suite initialization with a wrapper function. Whenever you call that function, a new suite state will be created.
|
|
85
|
-
|
|
86
|
-
### Example
|
|
87
|
-
|
|
88
|
-
```js
|
|
89
|
-
import { create } from 'vest';
|
|
90
|
-
|
|
91
|
-
function suite(data) {
|
|
92
|
-
return create(() => {
|
|
93
|
-
test('username', 'username is required', () => {
|
|
94
|
-
enforce(data.username).isNotEmpty();
|
|
95
|
-
});
|
|
96
|
-
})();
|
|
97
|
-
// Note that we're immediately invoking our suite
|
|
98
|
-
// so what we return is actually the suite result
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const result = suite({ username: 'Mike123' });
|
|
102
|
-
```
|
package/docs/test.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Using the `test` function
|
|
2
|
-
|
|
3
|
-
The `test` function is represents a single case in your validation suite. It accepts the following arguments:
|
|
4
|
-
|
|
5
|
-
| Name | Type | Optional | Description |
|
|
6
|
-
| ---------- | ---------- | -------- | ------------------------------------------------------------- |
|
|
7
|
-
| `name` | `String` | No | The name of the value or field being validated. |
|
|
8
|
-
| `message` | `String` | Yes | An error message to display to the user in case of a failure. |
|
|
9
|
-
| `callback` | `Function` | No | The actual validation logic for the given test. |
|
|
10
|
-
|
|
11
|
-
A test can either be synchronous or asynchronous, and it can either have a [severity](./warn) of `error` or of `warn`.
|
|
12
|
-
|
|
13
|
-
## Failing a test
|
|
14
|
-
|
|
15
|
-
There are three ways to fail a test:
|
|
16
|
-
|
|
17
|
-
### Throwing inside your test body (using enforce)
|
|
18
|
-
|
|
19
|
-
Just like in most unit testing frameworks, a validation fails whenever the test body throws an exception. [`Enforce`](./enforce) throws on failed validations.
|
|
20
|
-
When thrown with a string
|
|
21
|
-
|
|
22
|
-
```js
|
|
23
|
-
// const username = 'Gina.Vandervort';
|
|
24
|
-
// const password = 'Q3O';
|
|
25
|
-
|
|
26
|
-
test('username', 'Should be at least 3 characters long', () => {
|
|
27
|
-
enforce(username).longerThanOrEquals(3);
|
|
28
|
-
}); // this test passes
|
|
29
|
-
|
|
30
|
-
test('password', 'Should be at least 6 characters long', () => {
|
|
31
|
-
enforce(password).longerThanOrEquals(6); // an error is thrown here
|
|
32
|
-
}); // this test fails
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Alternatively, you can also throw a string value to use it as your test message. To do that, you need to omit the test message, and throw a string, for example - when using enforce.extend.
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
enforce.extend({
|
|
39
|
-
isChecked: value => {
|
|
40
|
-
return {
|
|
41
|
-
pass: !!value.checked,
|
|
42
|
-
message: () => 'value must be checked',
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
/*...*/
|
|
48
|
-
|
|
49
|
-
/*
|
|
50
|
-
tost = { checked: false }
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
test('tos', () => {
|
|
54
|
-
enforce(tos).isChecked(); // will fail with the message: "value must be checked"
|
|
55
|
-
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Explicitly returning false
|
|
59
|
-
|
|
60
|
-
To make it easy to migrate your existing validation logic into Vest, it also supports validations explicitly returning `false` (and not any other falsy value) to represent failures.
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
// const username = 'Gina.Vandervort';
|
|
64
|
-
// const password = 'Q3O';
|
|
65
|
-
|
|
66
|
-
test('username', 'Should be at least 3 characters long', () => {
|
|
67
|
-
return username.length >= 3; // = true
|
|
68
|
-
}); // this test passes
|
|
69
|
-
|
|
70
|
-
test('password', 'Should be at least 6 characters long', () => {
|
|
71
|
-
return password.length >= 6; // = false
|
|
72
|
-
}); // this test fails
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Rejecting a Promise
|
|
76
|
-
|
|
77
|
-
This is only true for async tests, more on that below.
|
|
78
|
-
|
|
79
|
-
## Asynchronous Tests
|
|
80
|
-
|
|
81
|
-
Sometimes you need to validate your data with information not present in your current context, for example - data from the server, such as username availability. In those cases, you need to go out to the server and fetch data as part of your validation logic.
|
|
82
|
-
|
|
83
|
-
An async test is declared by returning a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) from your test body. When the promise resolves, your test passes, and when your promise rejects, it fails.
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
// Example using a promise
|
|
87
|
-
test('name', 'I always fail', () => Promise.reject());
|
|
88
|
-
|
|
89
|
-
// Example using async/await
|
|
90
|
-
test('name', 'Already Taken', async () => {
|
|
91
|
-
return await doesUserExist(user);
|
|
92
|
-
});
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Rejecting with a message
|
|
96
|
-
|
|
97
|
-
When performing validations on the server, your server might need to respond with different error messages. When rejecting with a string value, your string value will be picked up as the message to show to the user.
|
|
98
|
-
|
|
99
|
-
```js
|
|
100
|
-
test('name', () =>
|
|
101
|
-
new Promise((resolve, reject) => {
|
|
102
|
-
fetch(`/checkUsername?name=${name}`)
|
|
103
|
-
.then(res => res.json)
|
|
104
|
-
.then(data => {
|
|
105
|
-
if (data.status === 'fail') {
|
|
106
|
-
reject(data.message); // rejects with message and marks the test as failing
|
|
107
|
-
} else {
|
|
108
|
-
resolve(); // completes. doesn't mark the test as failing
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}));
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## test.memo for memoized tests
|
|
115
|
-
|
|
116
|
-
In order to improve performance and runtime in heavy or long-running tests (such as async tests that go to the server), tests individual test results can be cached and saved for a later time, so whenever the exact same params appear again in the same runtime, the test result will be used from cache, instead of having to be re-evaluated.
|
|
117
|
-
|
|
118
|
-
### Usage:
|
|
119
|
-
|
|
120
|
-
Memoized tests are almost identical to regular tests, only with the added dependency array as the last argument. The dependency array is an array of items, that when identical (strict equality, `===`) to a previously presented array in the same test, its previous result will be used. You can see it as your cache key to the test result.
|
|
121
|
-
|
|
122
|
-
### Example:
|
|
123
|
-
|
|
124
|
-
```js
|
|
125
|
-
import { vest, test } from 'vest';
|
|
126
|
-
export default create(data => {
|
|
127
|
-
test.memo(
|
|
128
|
-
'username',
|
|
129
|
-
'username already exists',
|
|
130
|
-
() => doesUserExist(data.username),
|
|
131
|
-
[data.username]
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## test.each for dynamically creating tests from a table
|
|
137
|
-
|
|
138
|
-
Use test.each when you need to dynamically create tests from data, or when you have multiple tests that have the same overall structure.
|
|
139
|
-
|
|
140
|
-
test.each takes an array of arrays. The inner array contains the arguments that each of the tests will receive.
|
|
141
|
-
|
|
142
|
-
Because of the dynamic nature of the iterative tests, you can also dynamically construct the fieldName and the test message by providing a function instead of a string. Your array's content will be passed over as arguments to each of these functions.
|
|
143
|
-
|
|
144
|
-
```js
|
|
145
|
-
/*
|
|
146
|
-
const data = {
|
|
147
|
-
products: [
|
|
148
|
-
['Game Boy Color', 25],
|
|
149
|
-
['Speak & Spell', 22.5],
|
|
150
|
-
['Tamagotchi', 15],
|
|
151
|
-
['Connect Four', 7.88],
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
const suite = create(data => {
|
|
157
|
-
test.each(data.products)(
|
|
158
|
-
name => name,
|
|
159
|
-
'Price must be numeric and above zero.',
|
|
160
|
-
(_, price) => {
|
|
161
|
-
enforce(price).isNumeric().greaterThan(0);
|
|
162
|
-
}
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**Read next about:**
|
|
168
|
-
|
|
169
|
-
- [Warn only tests](./warn).
|
|
170
|
-
- [Grouping tests](./group).
|
|
171
|
-
- [Asserting with enforce](./enforce).
|
|
172
|
-
- [Skipping or including tests](./exclusion).
|