vest 3.2.8-dev-6d7c74 → 3.2.8

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.
Files changed (80) hide show
  1. package/README.md +115 -0
  2. package/any.d.ts +3 -0
  3. package/any.js +1 -0
  4. package/classNames.d.ts +14 -0
  5. package/classNames.js +1 -0
  6. package/docs/.nojekyll +0 -0
  7. package/docs/README.md +115 -0
  8. package/docs/_assets/favicon.ico +0 -0
  9. package/docs/_assets/vest-logo.png +0 -0
  10. package/docs/_sidebar.md +19 -0
  11. package/docs/_sidebar.md.bak +14 -0
  12. package/docs/cross_field_validations.md +79 -0
  13. package/docs/enforce.md +18 -0
  14. package/docs/enforce.md.bak +13 -0
  15. package/docs/exclusion.md +128 -0
  16. package/docs/getting_started.md +79 -0
  17. package/docs/group.md +142 -0
  18. package/docs/index.html +41 -0
  19. package/docs/migration.md +107 -0
  20. package/docs/n4s/compound.md +187 -0
  21. package/docs/n4s/custom.md +52 -0
  22. package/docs/n4s/external.md +54 -0
  23. package/docs/n4s/rules.md +1282 -0
  24. package/docs/n4s/template.md +53 -0
  25. package/docs/node.md +43 -0
  26. package/docs/optional.md +51 -0
  27. package/docs/result.md +238 -0
  28. package/docs/state.md +102 -0
  29. package/docs/test.md +172 -0
  30. package/docs/utilities.md +105 -0
  31. package/docs/warn.md +82 -0
  32. package/enforce.d.ts +230 -0
  33. package/esm/package.json +1 -0
  34. package/esm/vest.es.development.js +2493 -0
  35. package/esm/vest.es.production.js +2490 -0
  36. package/esm/vest.es.production.min.js +1 -0
  37. package/package.json +65 -12
  38. package/promisify.d.ts +7 -0
  39. package/{dist/umd/promisify.production.js → promisify.js} +1 -1
  40. package/schema.d.ts +26 -0
  41. package/schema.js +1 -0
  42. package/vest.cjs.development.js +2494 -0
  43. package/vest.cjs.production.js +2491 -0
  44. package/vest.cjs.production.min.js +1 -0
  45. package/vest.d.ts +254 -0
  46. package/vest.js +7 -0
  47. package/vest.umd.development.js +2711 -0
  48. package/vest.umd.production.js +2708 -0
  49. package/vest.umd.production.min.js +1 -0
  50. package/vestResult.d.ts +105 -0
  51. package/CHANGELOG.md +0 -94
  52. package/LICENSE +0 -21
  53. package/classnames/index.js +0 -7
  54. package/classnames/package.json +0 -1
  55. package/dist/cjs/classnames.development.js +0 -67
  56. package/dist/cjs/classnames.production.js +0 -1
  57. package/dist/cjs/promisify.development.js +0 -20
  58. package/dist/cjs/promisify.production.js +0 -1
  59. package/dist/cjs/vest.development.js +0 -1616
  60. package/dist/cjs/vest.production.js +0 -1
  61. package/dist/es/classnames.development.js +0 -65
  62. package/dist/es/classnames.production.js +0 -1
  63. package/dist/es/promisify.development.js +0 -18
  64. package/dist/es/promisify.production.js +0 -1
  65. package/dist/es/vest.development.js +0 -1604
  66. package/dist/es/vest.production.js +0 -1
  67. package/dist/umd/classnames.development.js +0 -73
  68. package/dist/umd/classnames.production.js +0 -1
  69. package/dist/umd/promisify.development.js +0 -26
  70. package/dist/umd/vest.development.js +0 -1622
  71. package/dist/umd/vest.production.js +0 -1
  72. package/index.js +0 -7
  73. package/promisify/index.js +0 -7
  74. package/promisify/package.json +0 -1
  75. package/types/classnames.d.ts +0 -71
  76. package/types/classnames.d.ts.map +0 -1
  77. package/types/promisify.d.ts +0 -61
  78. package/types/promisify.d.ts.map +0 -1
  79. package/types/vest.d.ts +0 -271
  80. package/types/vest.d.ts.map +0 -1
package/docs/group.md ADDED
@@ -0,0 +1,142 @@
1
+ # Grouping tests
2
+
3
+ In many cases it can be helpful to group tests together so you can include or exclude a portion of the suite with a single condition.
4
+ Similar to the `describe` and `context` features provided by unit testing frameworks, Vest provides `group`.
5
+
6
+ [Try on CodeSandbox (React)](https://codesandbox.io/s/vest-group-example-react-4i2ne)
7
+
8
+ ```js
9
+ import { create, test, group, enforce, skip } from 'vest';
10
+
11
+ create('authentication_form', data => {
12
+ skip.group(data.userExists ? 'signUp' : 'signIn');
13
+
14
+ test('userName', "Can't be empty", () => {
15
+ enforce(data.username).isNotEmpty();
16
+ });
17
+ test('password', "Can't be empty", () => {
18
+ enforce(data.password).isNotEmpty();
19
+ });
20
+
21
+ group('signIn', () => {
22
+ test(
23
+ 'userName',
24
+ 'User not found. Please check if you typed it correctly.',
25
+ findUserName(data.username)
26
+ );
27
+ });
28
+
29
+ group('signUp', () => {
30
+ test('email', 'Email already registered', isEmailRegistered(data.email));
31
+
32
+ test('age', 'You must be at least 18 years old to join', () => {
33
+ enforce(data.age).largerThanOrEquals(18);
34
+ });
35
+ });
36
+ });
37
+ ```
38
+
39
+ ## Why use `group` and not just wrap the tests with an `if` statement?
40
+
41
+ In many cases it is sufficient to just use an `if` statement. The benefit of using `group` is that when skipping (either using [skip or only](./exclusion)), Vest will merge the previous group result with the current suite. This is mostly suitable for cases like demonstrated in the first example of the multi stage form.
42
+
43
+ ## Use cases
44
+
45
+ ### 1. Multi stage form
46
+
47
+ You may have in your application a multi-screen form, in which you want to validate each screen individually, but submit it all at once.
48
+
49
+ ```js
50
+ // suite.js
51
+ import { create, test, group, enforce, only } from 'vest';
52
+
53
+ const suite = create('product-create', (data, currentTab) => {
54
+ only.group(currentScreen);
55
+
56
+ group('overview_tab', () => {
57
+ test('productTitle', 'Must be at least 5 chars.', () => {
58
+ enforce(data.productTitle).longerThanOrEquals(5);
59
+ });
60
+
61
+ test('productDescription', "Can't be longer than 2500 chars.", () => {
62
+ enforce(data.productDescription).shorterThanOrEquals(2500);
63
+ });
64
+
65
+ test('productTags', 'Please provide up to 5 tags', () => {
66
+ enforce(data.tags).lengthEquals(5);
67
+ });
68
+ });
69
+
70
+ group('pricing_tab', () => {
71
+ test('price', '5$ or more.', () => {
72
+ enforce(data.price).lte(5);
73
+ });
74
+
75
+ test('productExtras', "Can't be empty.", () => {
76
+ enforce(data.extras).isNotEmpty();
77
+ });
78
+ });
79
+ });
80
+
81
+ export default suite;
82
+ ```
83
+
84
+ ```js
85
+ // myFeature.js
86
+
87
+ suite(data, 'overview_tab'); // will only validate 'overview_tab' group
88
+ suite(data, 'pricing_tab'); // will only validate 'pricing_tab' group
89
+ ```
90
+
91
+ ### 2. Skipping tests with shared fields
92
+
93
+ You sometimes want to skip some tests on a certain condition, but still run other tests with the same field-name.
94
+
95
+ In the example below, we don't mind skipping the `balance` field directly, but if we skip the `quantity` field directly, it won't be tested at all - even though it has one test outside of the group. That's why we skip the `used_promo`.
96
+
97
+ ```js
98
+ import { create, test, group, enforce, skip } from 'vest';
99
+
100
+ const suite = create('checkout_form', data => {
101
+ if (!data.usedPromo) skip.group('used_promo');
102
+ if (!data.paysWithBalance) skip.group('balance');
103
+
104
+ test(
105
+ 'balance',
106
+ 'Balance is lower than product price',
107
+ hasSufficientFunds(data.productId)
108
+ );
109
+
110
+ test('quantity', `Quantity on this item is limited to ${data.limit}`, () => {
111
+ enforce(data.quantity).lessThanOrEquals(data.limit);
112
+ });
113
+
114
+ group('used_promo', () => {
115
+ test(
116
+ 'quantity',
117
+ 'promo code purchases are limited to one item only',
118
+ () => {
119
+ enforce(data.quantity).equals(1);
120
+ }
121
+ );
122
+
123
+ test(
124
+ 'promoCode',
125
+ 'Promo code can only be used once',
126
+ isPromoCodeUsed(data.usedPromo)
127
+ );
128
+ });
129
+ });
130
+ ```
131
+
132
+ ## Querying the result object for groups
133
+
134
+ Groups represent a portion of your validation suite, so when using `group`, you are likely to need to get the group-specific validation results.
135
+ Your result object exposes the following methods:
136
+
137
+ - [_hasErrorsByGroup_](./result#haserrorsbygroup-and-haswarningsbygroup-functions)
138
+ - [_hasWarningsByGroup_](./result#haserrorsbygroup-and-haswarningsbygroup-functions)
139
+ - [_hasErrorsByGroup_](./result#geterrorsbygroup-and-getwarningsbygroup-functions)
140
+ - [_hasWarningsByGroup_](./result#geterrorsbygroup-and-getwarningsbygroup-functions)
141
+
142
+ Read more about these methods in [the result object](./result).
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Vest - Declarative Validations</title>
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
+ <meta name="description" content="Validation Test" />
8
+ <meta
9
+ name="viewport"
10
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
11
+ />
12
+ <link rel="icon" href="./_assets/favicon.ico" />
13
+ <link
14
+ rel="stylesheet"
15
+ href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"
16
+ />
17
+ <link
18
+ rel="stylesheet"
19
+ href="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/themes/prism-tomorrow.css"
20
+ />
21
+ </head>
22
+ <body>
23
+ <div id="app"></div>
24
+ <script>
25
+ window.$docsify = {
26
+ name: 'vest',
27
+ logo: '/_assets/vest-logo.png',
28
+ repo: 'https://github.com/ealush/vest',
29
+ search: 'auto',
30
+ loadSidebar: true,
31
+ subMaxLevel: 2,
32
+ themeColor: '#5BA9D8',
33
+ };
34
+ </script>
35
+ <script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
36
+ <script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0"></script>
37
+ <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
38
+ <script src="//unpkg.com/vest"></script>
39
+ <script src="./_assets/index.js"></script>
40
+ </body>
41
+ </html>
@@ -0,0 +1,107 @@
1
+ # Migration guides
2
+
3
+ ## V2 to V3
4
+
5
+ Vest version 3 comes with many new features, yet with a reduced bundle size. To achieve this, some redundant interfaces were removed. All v2 capabilities still exist, but the way to use some of them changed.
6
+
7
+ **Replaced interfaces**
8
+
9
+ ### Removed: vest.get()
10
+
11
+ From now on, use suite.get() to get the latest validation result.
12
+
13
+ #### v2
14
+
15
+ ```js
16
+ const suite = vest.create('user_form', () => {
17
+ /*...*/
18
+ });
19
+
20
+ vest.get('user_form'); // Returns the most recent validation result
21
+ ```
22
+
23
+ #### v3
24
+
25
+ ```js
26
+ const suite = create(() => {
27
+ /*...*/
28
+ });
29
+
30
+ suite.get(); // Returns the most recent validation result
31
+ ```
32
+
33
+ ### Removed: vest.reset() // To reset suite state
34
+
35
+ From now on, use suite.reset() to reset the validation result.
36
+
37
+ #### v2
38
+
39
+ ```js
40
+ const suite = vest.create('user_form', () => {
41
+ /*...*/
42
+ });
43
+
44
+ vest.reset('user_form'); // Resets the validity state
45
+ ```
46
+
47
+ #### v3
48
+
49
+ ```js
50
+ const suite = create(() => {
51
+ /*...*/
52
+ });
53
+
54
+ suite.reset(); // Resets the validity state
55
+ ```
56
+
57
+ ### Removed: vest.draft() // To retrieve intermediate result
58
+
59
+ From now on, use suite.get() to get the validation result.
60
+
61
+ #### v2
62
+
63
+ ```js
64
+ const suite = vest.create('user_form', () => {
65
+ if (vest.draft().hasErrors('username')) {
66
+ /* ... */
67
+ }
68
+ });
69
+ ```
70
+
71
+ #### v3
72
+
73
+ ```js
74
+ const suite = create('user_form', () => {
75
+ skipWhen(suite.get().hasErrors('username'), () => {
76
+ /* ... */
77
+ });
78
+ });
79
+ ```
80
+
81
+ ### Removed: validate() // For non persistent validations
82
+
83
+ The stateless validate export is not needed anymore due to a change in the state structure.
84
+
85
+ #### v2
86
+
87
+ ```js
88
+ import { validate } from 'vest';
89
+
90
+ const result = data =>
91
+ validate('user_form', () => {
92
+ /*...*/
93
+ })();
94
+ ```
95
+
96
+ #### v3
97
+
98
+ ```js
99
+ import { create } from 'vest';
100
+
101
+ const suite = data =>
102
+ create(() => {
103
+ /* ... */
104
+ })();
105
+
106
+ const result = suite({ username: 'example' });
107
+ ```
@@ -0,0 +1,187 @@
1
+ # Shape and schema validation
2
+
3
+ Alongside the list of rules that only accept data provided by the user, enforce also supports compound rules - these are rules that accept other rules as their arguments. These rules let you validate more complex scenarios with the ergonomics of enforce.
4
+
5
+ - [enforce.anyOf() - either/or validations](#anyof)
6
+ - [enforce.allOf() - all/and validations](#allof)
7
+ - [enforce.shape() - Object's shape matching](#shape)
8
+ - [enforce.optional() - nullable keys](#optional)
9
+ - [enforec.loose() - loose shape matching](#loose)
10
+ - [enforce.isArrayOf() - array shape matching](#isarrayof)
11
+
12
+ ## enforce.anyOf() - either/or validations :id=anyof
13
+
14
+ Sometimes a value has more than one valid possibilities, `any` lets us validate that a value passes _at least_ one of the supplied rules.
15
+
16
+ ```js
17
+ enforce(value).anyOf(enforce.isString(), enforce.isArray()).isNotEmpty();
18
+ // A valid value would either an array or a string.
19
+ ```
20
+
21
+ ## enforce.allOf() - all/and validations :id=allof
22
+
23
+ `allOf` lets us validate that a value passes _all_ of the supplied rules or templates.
24
+
25
+ enforce(value).allOf(
26
+ enforce.isArray(),
27
+ enforce.longerThan(2)
28
+ );
29
+
30
+ This can be even more useful when combined with shapes and templates:
31
+
32
+ ```js
33
+ const User = enforce.template(
34
+ enforce.loose({
35
+ id: enforce.isNumber()
36
+ name: enforce.shape({
37
+ first: enforce.isString(),
38
+ last: enforce.isString(),
39
+ middle: enforce.optional(enforce.isString()),
40
+ }),
41
+ })
42
+ );
43
+
44
+ const DisabledAccount = enforce.template(
45
+ enforce.loose({
46
+ disabled: enforce.equals(true)
47
+ })
48
+ )
49
+
50
+ enforce(value).allOf(
51
+ User,
52
+ DisabledAccount
53
+ );
54
+ // A valid is string and longer then 5.
55
+ ```
56
+
57
+ ## enforce.shape() - Lean schema validation. :id=shape
58
+
59
+ `enforce.shape()` validates the structure of an object.
60
+
61
+ ```js
62
+ enforce({
63
+ firstName: 'Rick',
64
+ lastName: 'Sanchez',
65
+ age: 70,
66
+ }).shape({
67
+ firstName: enforce.isString(),
68
+ lastName: enforce.isString(),
69
+ age: enforce.isNumber(),
70
+ });
71
+ ```
72
+
73
+ You may also chain your validation rules:
74
+
75
+ ```js
76
+ enforce({
77
+ age: 22,
78
+ }).shape({
79
+ age: enforce.isNumber().isBetween(0, 150),
80
+ });
81
+ ```
82
+
83
+ You may also nest calls to shape in order to validate a deeply nested object.
84
+
85
+ ```js
86
+ enforce({
87
+ user: {
88
+ name: {
89
+ first: 'Joseph',
90
+ last: 'Weil',
91
+ },
92
+ },
93
+ }).shape({
94
+ user: enforce.shape({
95
+ name: enforce.shape({
96
+ first: enforce.isString(),
97
+ last: enforce.isString(),
98
+ }),
99
+ }),
100
+ });
101
+ ```
102
+
103
+ ### enforce.optional() - nullable keys :id=optional
104
+
105
+ -- Optional can only be used within enforce.shape().
106
+
107
+ In regular cases, a missing key in the data object would cause an error to be thrown. To prevent that from happening, mark your optional keys with `enforce.optional`.
108
+
109
+ enforce.optional will pass validations of a key that's either not defined, undefined or null.
110
+
111
+ `enforce.optional` takes as its arguments all the rules that the value must pass.
112
+
113
+ ```js
114
+ enforce({
115
+ user: {
116
+ name: {
117
+ first: 'Joseph',
118
+ last: 'Weil',
119
+ },
120
+ },
121
+ }).shape({
122
+ user: enforce.shape({
123
+ name: enforce.shape({
124
+ first: enforce.isString(),
125
+ last: enforce.isString(),
126
+ middle: enforce.optional(enforce.isString(), enforce.longerThan(3)),
127
+ }),
128
+ }),
129
+ });
130
+ ```
131
+
132
+ ## enforec.loose() - loose shape matching :id=loose
133
+
134
+ By default, shape will treat excess keys in your data object as validation errors. If you wish to allow support for excess keys in your object's shape, you can use `enforce.loose()` which is a shorthand to `enforce.shape(data, shape, { loose: true })`.
135
+
136
+ ```js
137
+ enforce({ name: 'Laura', code: 'x23' }).shape({ name: enforce.isString() });
138
+ // 🚨 This will throw an error because `code` is not defined in the shape
139
+ ```
140
+
141
+ ```js
142
+ enforce({ name: 'Laura', code: 'x23' }).loose({ name: enforce.isString() });
143
+ // ✅ This will pass with `code` not being validated
144
+ ```
145
+
146
+ ## enforce.isArrayOf() - array shape matching :id=isarrayof
147
+
148
+ enforce.isArrayOf can be used to determine the allowed types and values within an array. It will run against each element in the array, and will only pass if all items meet at least one of the validation rules.
149
+
150
+ ```js
151
+ enforce([1, 2, 'hello!']).isArrayOf(enforce.isString(), enforce.isNumber());
152
+ ```
153
+
154
+ You can also combine `isArrayOf` with other rules to validate other array properties:
155
+
156
+ ```js
157
+ enforce(someArrayValue)
158
+ .isArrayOf(enforce.isString(), enforce.isNumber().lessThan(3))
159
+ .longerThan(2);
160
+ ```
161
+
162
+ And as part of shape:
163
+
164
+ ```js
165
+ enforce({ data: [1, 2, 3] }).shape({
166
+ data: enforce.isArrayOf(enforce.isNumber()),
167
+ });
168
+ ```
169
+
170
+ ## enforce.oneOf()
171
+
172
+ enforce.oneOf can be used to determine if _exactly_ one of the rules applies. It will run against rule in the array, and will only pass if exactly one rule applies.
173
+
174
+ ```js
175
+ enforce(value).oneOf(
176
+ enforce.isString(),
177
+ enforce.isNumber(),
178
+ enforce.longerThan(1)
179
+ );
180
+
181
+ /*
182
+ value = 1 -> ✅ (value is a number)
183
+ value = "1" -> ✅ (value is string)
184
+ value = [1, 2] -> ✅ (value is longer than 1)
185
+ value = "12" -> 🚨 (value is both a string and longer than 1)
186
+ */
187
+ ```
@@ -0,0 +1,52 @@
1
+ # Creating Custom Rules
2
+
3
+ To make it easier to reuse logic across your application, sometimes you would want to encapsulate bits of logic in rules that you can use later on, for example, "what's considered a valid email".
4
+
5
+ Rules are called with the argument passed to enforce(x) followed by the arguments passed to `.yourRule(y, z)`.
6
+
7
+ ```js
8
+ enforce.extend({
9
+ yourRule(x, y, z) {
10
+ return {
11
+ pass: true,
12
+ message: () => '',
13
+ };
14
+ },
15
+ });
16
+ ```
17
+
18
+ ```js
19
+ enforce.extend({
20
+ isValidEmail: value => value.indexOf('@') > -1,
21
+ hasKey: (value, key) => value.hasOwnProperty(key),
22
+ passwordsMatch: (passConfirm, options) =>
23
+ passConfirm === options.passConfirm && options.passIsValid,
24
+ });
25
+
26
+ enforce(user.email).isValidEmail();
27
+ ```
28
+
29
+ ## Custom rules return value
30
+
31
+ Rules can either return boolean indicating success or failure, or an object with two keys. `pass` indicates whether the validation is successful or not, and message provides a function with no arguments that return an error message in case of failure. Thus, when pass is false, message should return the error message for when enforce(x).yourRule() fails.
32
+
33
+ ```js
34
+ enforce.extend({
35
+ isWithinRange(received, floor, ceiling) {
36
+ const pass = received >= floor && received <= ceiling;
37
+ if (pass) {
38
+ return {
39
+ message: () =>
40
+ `expected ${received} not to be within range ${floor} - ${ceiling}`,
41
+ pass: true,
42
+ };
43
+ } else {
44
+ return {
45
+ message: () =>
46
+ `expected ${received} to be within range ${floor} - ${ceiling}`,
47
+ pass: false,
48
+ };
49
+ }
50
+ },
51
+ });
52
+ ```
@@ -0,0 +1,54 @@
1
+ # Consuming external rules
2
+
3
+ Enforce comes with the bare minimum of rules needed for input validation, not assuming your business logic constraints.
4
+
5
+ In some cases you might require more validations such as `isEmail` or `isPhoneNumber`. Enforce intentionally does not include those, since those validations may not necessarily reflect the way those validations should work in your app.
6
+
7
+ Luckily, there are numerous packages that can be used along with enforce to add those validations. One of the most popular, and most compatible is `validator.js`.
8
+
9
+ ```
10
+ npm i validator
11
+ ```
12
+
13
+ Validator.js is a pretty big package. To prevent it from unnecessarily increasing your bundle size for rules you don't use, import the ones you use individually.
14
+
15
+ Then add those rules with `enforce.extend`:
16
+
17
+ ```js
18
+ import isEmail from 'validator/es/lib/isEmail';
19
+ import isMobilePhone from 'validator/es/lib/isMobilePhone';
20
+
21
+ enforce.extend({ isEmail, isMobilePhone });
22
+
23
+ enforce('example@example.com').isEmail(); // ✅
24
+ enforce('example[at]example[dot]com').isEmail(); // 🚨
25
+ ```
26
+
27
+ A full list of the supported validator.js rules can be found on [npmjs.com/package/validator](https://www.npmjs.com/package/validator). Some common rules are:
28
+
29
+ - isAfter
30
+ - isBefore
31
+ - isBtcAddress
32
+ - isCreditCard
33
+ - isCurrency
34
+ - isDate
35
+ - isEmail
36
+ - isFQDN
37
+ - isIBAN
38
+ - isIdentityCard
39
+ - isIP
40
+ - isIPRange
41
+ - isJSON
42
+ - isJWT
43
+ - isMACAddress
44
+ - isMD5
45
+ - isMimeType
46
+ - isMobilePhone
47
+ - isMongoId
48
+ - isPassportNumber
49
+ - isPort
50
+ - isPostalCode
51
+ - isSlug
52
+ - isURL
53
+ - isTaxID
54
+ - isUUID