vest 4.0.0-dev-7acb76 → 4.0.0-dev-1aae50

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 (73) hide show
  1. package/CHANGELOG.md +70 -50
  2. package/README.md +2 -112
  3. package/dist/cjs/classnames.development.js +3 -3
  4. package/dist/cjs/classnames.production.js +1 -1
  5. package/dist/cjs/compose.js +7 -0
  6. package/dist/cjs/compounds.js +7 -0
  7. package/dist/cjs/enforce/compose.development.js +139 -0
  8. package/dist/cjs/enforce/compose.production.js +1 -0
  9. package/dist/cjs/enforce/compounds.development.js +132 -0
  10. package/dist/cjs/enforce/compounds.production.js +1 -0
  11. package/dist/cjs/enforce/package.json +1 -0
  12. package/dist/cjs/enforce/schema.development.js +144 -0
  13. package/dist/cjs/enforce/schema.production.js +1 -0
  14. package/dist/cjs/promisify.development.js +1 -1
  15. package/dist/cjs/promisify.production.js +1 -1
  16. package/dist/cjs/schema.js +7 -0
  17. package/dist/cjs/vest.development.js +582 -1083
  18. package/dist/cjs/vest.production.js +1 -1
  19. package/dist/es/classnames.development.js +3 -3
  20. package/dist/es/classnames.production.js +1 -1
  21. package/dist/es/enforce/compose.development.js +137 -0
  22. package/dist/es/enforce/compose.production.js +1 -0
  23. package/dist/es/enforce/compounds.development.js +130 -0
  24. package/dist/es/enforce/compounds.production.js +1 -0
  25. package/dist/es/enforce/package.json +1 -0
  26. package/dist/es/enforce/schema.development.js +140 -0
  27. package/dist/es/enforce/schema.production.js +1 -0
  28. package/dist/es/promisify.development.js +1 -1
  29. package/dist/es/promisify.production.js +1 -1
  30. package/dist/es/vest.development.js +577 -1084
  31. package/dist/es/vest.production.js +1 -1
  32. package/dist/umd/classnames.development.js +3 -3
  33. package/dist/umd/classnames.production.js +1 -1
  34. package/dist/umd/enforce/compose.development.js +143 -0
  35. package/dist/umd/enforce/compose.production.js +1 -0
  36. package/dist/umd/enforce/compounds.development.js +136 -0
  37. package/dist/umd/enforce/compounds.production.js +1 -0
  38. package/dist/umd/enforce/schema.development.js +148 -0
  39. package/dist/umd/enforce/schema.production.js +1 -0
  40. package/dist/umd/promisify.development.js +1 -1
  41. package/dist/umd/promisify.production.js +1 -1
  42. package/dist/umd/vest.development.js +1693 -2197
  43. package/dist/umd/vest.production.js +1 -1
  44. package/enforce/compose/package.json +7 -0
  45. package/enforce/compounds/package.json +7 -0
  46. package/enforce/schema/package.json +7 -0
  47. package/package.json +107 -13
  48. package/testUtils/mockThrowError.ts +16 -0
  49. package/types/classnames.d.ts +2 -2
  50. package/types/enforce/compose.d.ts +134 -0
  51. package/types/enforce/compounds.d.ts +146 -0
  52. package/types/enforce/schema.d.ts +151 -0
  53. package/types/vest.d.ts +31 -196
  54. package/docs/.nojekyll +0 -0
  55. package/docs/README.md +0 -113
  56. package/docs/_assets/favicon.ico +0 -0
  57. package/docs/_assets/vest-logo.png +0 -0
  58. package/docs/_sidebar.md +0 -14
  59. package/docs/cross_field_validations.md +0 -33
  60. package/docs/enforce.md +0 -11
  61. package/docs/exclusion.md +0 -129
  62. package/docs/getting_started.md +0 -72
  63. package/docs/group.md +0 -142
  64. package/docs/index.html +0 -41
  65. package/docs/migration.md +0 -202
  66. package/docs/n4s/rules.md +0 -1282
  67. package/docs/node.md +0 -36
  68. package/docs/optional.md +0 -103
  69. package/docs/result.md +0 -249
  70. package/docs/state.md +0 -102
  71. package/docs/test.md +0 -172
  72. package/docs/utilities.md +0 -109
  73. package/docs/warn.md +0 -82
@@ -2,63 +2,22 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- /*! *****************************************************************************
6
- Copyright (c) Microsoft Corporation.
7
-
8
- Permission to use, copy, modify, and/or distribute this software for any
9
- purpose with or without fee is hereby granted.
10
-
11
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
- PERFORMANCE OF THIS SOFTWARE.
18
- ***************************************************************************** */
19
-
20
- var __assign = function() {
21
- __assign = Object.assign || function __assign(t) {
22
- for (var s, i = 1, n = arguments.length; i < n; i++) {
23
- s = arguments[i];
24
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
25
- }
26
- return t;
27
- };
28
- return __assign.apply(this, arguments);
29
- };
30
-
31
- function __spreadArray(to, from, pack) {
32
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
33
- if (ar || !(i in from)) {
34
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
35
- ar[i] = from[i];
36
- }
37
- }
38
- return to.concat(ar || from);
39
- }
5
+ var n4s = require('n4s');
6
+ var context$1 = require('context');
40
7
 
41
8
  var assign = Object.assign;
42
9
 
10
+ /**
11
+ * @returns a unique numeric id.
12
+ */
13
+ var genId = (function (n) { return function () {
14
+ return "" + n++;
15
+ }; })(0);
16
+
43
17
  function isFunction(value) {
44
18
  return typeof value === 'function';
45
19
  }
46
20
 
47
- function mapFirst(array, callback) {
48
- var broke = false;
49
- var breakoutValue = null;
50
- for (var i = 0; i < array.length; i++) {
51
- callback(array[i], breakout, i);
52
- if (broke) {
53
- return breakoutValue;
54
- }
55
- }
56
- function breakout(value) {
57
- broke = true;
58
- breakoutValue = value;
59
- }
60
- }
61
-
62
21
  function optionalFunctionValue(value) {
63
22
  var args = [];
64
23
  for (var _i = 1; _i < arguments.length; _i++) {
@@ -72,380 +31,6 @@ function defaultTo(callback, defaultValue) {
72
31
  return (_a = optionalFunctionValue(callback)) !== null && _a !== void 0 ? _a : defaultValue;
73
32
  }
74
33
 
75
- function ruleReturn(pass, message) {
76
- var output = { pass: pass };
77
- if (message) {
78
- output.message = message;
79
- }
80
- return output;
81
- }
82
- function failing() {
83
- return ruleReturn(false);
84
- }
85
- function passing() {
86
- return ruleReturn(true);
87
- }
88
- function defaultToFailing(callback) {
89
- return defaultTo(callback, failing());
90
- }
91
- function defaultToPassing(callback) {
92
- return defaultTo(callback, passing());
93
- }
94
-
95
- function runLazyRule(lazyRule, currentValue) {
96
- try {
97
- return lazyRule.run(currentValue);
98
- }
99
- catch (_a) {
100
- return failing();
101
- }
102
- }
103
-
104
- function allOf(value) {
105
- var rules = [];
106
- for (var _i = 1; _i < arguments.length; _i++) {
107
- rules[_i - 1] = arguments[_i];
108
- }
109
- return defaultToPassing(mapFirst(rules, function (rule, breakout) {
110
- var res = runLazyRule(rule, value);
111
- if (!res.pass) {
112
- breakout(res);
113
- }
114
- }));
115
- }
116
-
117
- function anyOf(value) {
118
- var rules = [];
119
- for (var _i = 1; _i < arguments.length; _i++) {
120
- rules[_i - 1] = arguments[_i];
121
- }
122
- return defaultToFailing(mapFirst(rules, function (rule, breakout) {
123
- var res = runLazyRule(rule, value);
124
- if (res.pass) {
125
- breakout(res);
126
- }
127
- }));
128
- }
129
-
130
- function noneOf(value) {
131
- var rules = [];
132
- for (var _i = 1; _i < arguments.length; _i++) {
133
- rules[_i - 1] = arguments[_i];
134
- }
135
- return defaultToPassing(mapFirst(rules, function (rule, breakout) {
136
- var res = runLazyRule(rule, value);
137
- if (res.pass) {
138
- breakout(failing());
139
- }
140
- }));
141
- }
142
-
143
- function bindNot(fn) {
144
- return function () {
145
- var args = [];
146
- for (var _i = 0; _i < arguments.length; _i++) {
147
- args[_i] = arguments[_i];
148
- }
149
- return !fn.apply(void 0, args);
150
- };
151
- }
152
-
153
- function lengthEquals(value, arg1) {
154
- return value.length === Number(arg1);
155
- }
156
- var lengthNotEquals = bindNot(lengthEquals);
157
-
158
- function longerThan(value, arg1) {
159
- return value.length > Number(arg1);
160
- }
161
-
162
- function oneOf(value) {
163
- var rules = [];
164
- for (var _i = 1; _i < arguments.length; _i++) {
165
- rules[_i - 1] = arguments[_i];
166
- }
167
- var passing = [];
168
- rules.some(function (rule) {
169
- if (longerThan(passing, 1)) {
170
- return false;
171
- }
172
- var res = runLazyRule(rule, value);
173
- if (res.pass) {
174
- passing.push(res);
175
- }
176
- });
177
- return ruleReturn(lengthEquals(passing, 1));
178
- }
179
-
180
- function isNull(value) {
181
- return value === null;
182
- }
183
- var isNotNull = bindNot(isNull);
184
-
185
- function isUndefined(value) {
186
- return value === undefined;
187
- }
188
- var isNotUndefined = bindNot(isUndefined);
189
-
190
- function optional$1(value, ruleChain) {
191
- if (isUndefined(value) || isNull(value)) {
192
- return passing();
193
- }
194
- return runLazyRule(ruleChain, value);
195
- }
196
-
197
- function compounds() {
198
- return { allOf: allOf, anyOf: anyOf, noneOf: noneOf, oneOf: oneOf, optional: optional$1 };
199
- }
200
-
201
- function isStringValue(v) {
202
- return String(v) === v;
203
- }
204
-
205
- function endsWith(value, arg1) {
206
- return isStringValue(value) && isStringValue(arg1) && value.endsWith(arg1);
207
- }
208
- var doesNotEndWith = bindNot(endsWith);
209
-
210
- function equals(value, arg1) {
211
- return value === arg1;
212
- }
213
- var notEquals = bindNot(equals);
214
-
215
- function isNumeric(value) {
216
- var str = String(value);
217
- var num = Number(value);
218
- var result = !isNaN(parseFloat(str)) && !isNaN(Number(value)) && isFinite(num);
219
- return Boolean(result);
220
- }
221
- var isNotNumeric = bindNot(isNumeric);
222
-
223
- function greaterThan(value, gt) {
224
- return isNumeric(value) && isNumeric(gt) && Number(value) > Number(gt);
225
- }
226
-
227
- function greaterThanOrEquals(value, gte) {
228
- return isNumeric(value) && isNumeric(gte) && Number(value) >= Number(gte);
229
- }
230
-
231
- // The module is named "isArrayValue" since it
232
- // is conflicting with a nested npm dependency.
233
- // We may need to revisit this in the future.
234
- function isArray(value) {
235
- return Boolean(Array.isArray(value));
236
- }
237
- var isNotArray = bindNot(isArray);
238
-
239
- function inside(value, arg1) {
240
- if (isArray(arg1)) {
241
- return arg1.indexOf(value) !== -1;
242
- }
243
- // both value and arg1 are strings
244
- if (isStringValue(arg1) && isStringValue(value)) {
245
- return arg1.indexOf(value) !== -1;
246
- }
247
- return false;
248
- }
249
- var notInside = bindNot(inside);
250
-
251
- function lessThanOrEquals(value, lte) {
252
- return isNumeric(value) && isNumeric(lte) && Number(value) <= Number(lte);
253
- }
254
-
255
- function isBetween(value, min, max) {
256
- return greaterThanOrEquals(value, min) && lessThanOrEquals(value, max);
257
- }
258
- var isNotBetween = bindNot(isBetween);
259
-
260
- function isBlank(value) {
261
- return isStringValue(value) && !value.trim();
262
- }
263
- var isNotBlank = bindNot(isBlank);
264
-
265
- function isBoolean(value) {
266
- return !!value === value;
267
- }
268
-
269
- var isNotBoolean = bindNot(isBoolean);
270
-
271
- /**
272
- * A safe hasOwnProperty access
273
- */
274
- function hasOwnProperty(obj, key) {
275
- return Object.prototype.hasOwnProperty.call(obj, key);
276
- }
277
-
278
- function isNumber(value) {
279
- return Boolean(typeof value === 'number');
280
- }
281
- var isNotNumber = bindNot(isNumber);
282
-
283
- function isEmpty(value) {
284
- if (!value) {
285
- return true;
286
- }
287
- else if (isNumber(value)) {
288
- return value === 0;
289
- }
290
- else if (hasOwnProperty(value, 'length')) {
291
- return lengthEquals(value, 0);
292
- }
293
- else if (typeof value === 'object') {
294
- return lengthEquals(Object.keys(value), 0);
295
- }
296
- return true;
297
- }
298
- var isNotEmpty = bindNot(isEmpty);
299
-
300
- /**
301
- * Validates that a given value is an even number
302
- */
303
- var isEven = function (value) {
304
- if (isNumeric(value)) {
305
- return value % 2 === 0;
306
- }
307
- return false;
308
- };
309
-
310
- function isNaN$1(value) {
311
- return Number.isNaN(value);
312
- }
313
- var isNotNaN = bindNot(isNaN$1);
314
-
315
- function isNegative(value) {
316
- if (isNumeric(value)) {
317
- return Number(value) < 0;
318
- }
319
- return false;
320
- }
321
- var isPositive = bindNot(isNegative);
322
-
323
- /**
324
- * Validates that a given value is an odd number
325
- */
326
- var isOdd = function (value) {
327
- if (isNumeric(value)) {
328
- return value % 2 !== 0;
329
- }
330
- return false;
331
- };
332
-
333
- var isNotString = bindNot(isStringValue);
334
-
335
- function isTruthy(value) {
336
- return !!value;
337
- }
338
- var isFalsy = bindNot(isTruthy);
339
-
340
- function lessThan(value, lt) {
341
- return isNumeric(value) && isNumeric(lt) && Number(value) < Number(lt);
342
- }
343
-
344
- function longerThanOrEquals(value, arg1) {
345
- return value.length >= Number(arg1);
346
- }
347
-
348
- function matches(value, regex) {
349
- if (regex instanceof RegExp) {
350
- return regex.test(value);
351
- }
352
- else if (isStringValue(regex)) {
353
- return new RegExp(regex).test(value);
354
- }
355
- else {
356
- return false;
357
- }
358
- }
359
- var notMatches = bindNot(matches);
360
-
361
- function numberEquals(value, eq) {
362
- return isNumeric(value) && isNumeric(eq) && Number(value) === Number(eq);
363
- }
364
- var numberNotEquals = bindNot(numberEquals);
365
-
366
- function condition(value, callback) {
367
- try {
368
- return callback(value);
369
- }
370
- catch (_a) {
371
- return false;
372
- }
373
- }
374
-
375
- function shorterThan(value, arg1) {
376
- return value.length < Number(arg1);
377
- }
378
-
379
- function shorterThanOrEquals(value, arg1) {
380
- return value.length <= Number(arg1);
381
- }
382
-
383
- function startsWith(value, arg1) {
384
- return isStringValue(value) && isStringValue(arg1) && value.startsWith(arg1);
385
- }
386
- var doesNotStartWith = bindNot(startsWith);
387
-
388
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, max-lines-per-function
389
- function rules() {
390
- return {
391
- condition: condition,
392
- doesNotEndWith: doesNotEndWith,
393
- doesNotStartWith: doesNotStartWith,
394
- endsWith: endsWith,
395
- equals: equals,
396
- greaterThan: greaterThan,
397
- greaterThanOrEquals: greaterThanOrEquals,
398
- gt: greaterThan,
399
- gte: greaterThanOrEquals,
400
- inside: inside,
401
- isArray: isArray,
402
- isBetween: isBetween,
403
- isBlank: isBlank,
404
- isBoolean: isBoolean,
405
- isEmpty: isEmpty,
406
- isEven: isEven,
407
- isFalsy: isFalsy,
408
- isNaN: isNaN$1,
409
- isNegative: isNegative,
410
- isNotArray: isNotArray,
411
- isNotBetween: isNotBetween,
412
- isNotBlank: isNotBlank,
413
- isNotBoolean: isNotBoolean,
414
- isNotEmpty: isNotEmpty,
415
- isNotNaN: isNotNaN,
416
- isNotNull: isNotNull,
417
- isNotNumber: isNotNumber,
418
- isNotNumeric: isNotNumeric,
419
- isNotString: isNotString,
420
- isNotUndefined: isNotUndefined,
421
- isNull: isNull,
422
- isNumber: isNumber,
423
- isNumeric: isNumeric,
424
- isOdd: isOdd,
425
- isPositive: isPositive,
426
- isString: isStringValue,
427
- isTruthy: isTruthy,
428
- isUndefined: isUndefined,
429
- lengthEquals: lengthEquals,
430
- lengthNotEquals: lengthNotEquals,
431
- lessThan: lessThan,
432
- lessThanOrEquals: lessThanOrEquals,
433
- longerThan: longerThan,
434
- longerThanOrEquals: longerThanOrEquals,
435
- lt: lessThan,
436
- lte: lessThanOrEquals,
437
- matches: matches,
438
- notEquals: notEquals,
439
- notInside: notInside,
440
- notMatches: notMatches,
441
- numberEquals: numberEquals,
442
- numberNotEquals: numberNotEquals,
443
- shorterThan: shorterThan,
444
- shorterThanOrEquals: shorterThanOrEquals,
445
- startsWith: startsWith
446
- };
447
- }
448
-
449
34
  /**
450
35
  * Throws a timed out error.
451
36
  */
@@ -458,349 +43,6 @@ function throwErrorDeferred(devMessage, productionMessage) {
458
43
  }, 0);
459
44
  }
460
45
 
461
- // eslint-disable-next-line max-lines-per-function
462
- function createContext(init) {
463
- var storage = { ancestry: [] };
464
- return {
465
- bind: bind,
466
- run: run,
467
- use: use,
468
- useX: useX
469
- };
470
- function useX(errorMessage) {
471
- var _a;
472
- return ((_a = storage.ctx) !== null && _a !== void 0 ? _a : throwError(defaultTo(errorMessage, 'Context was used after it was closed')));
473
- }
474
- function run(ctxRef, fn) {
475
- var _a;
476
- var parentContext = use();
477
- var out = assign({}, parentContext ? parentContext : {}, (_a = init === null || init === void 0 ? void 0 : init(ctxRef, parentContext)) !== null && _a !== void 0 ? _a : ctxRef);
478
- var ctx = set(Object.freeze(out));
479
- storage.ancestry.unshift(ctx);
480
- var res = fn(ctx);
481
- clear();
482
- return res;
483
- }
484
- function bind(ctxRef, fn) {
485
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
486
- // @ts-ignore - this one's pretty hard to get right
487
- var returnedFn = function () {
488
- var runTimeArgs = [];
489
- for (var _i = 0; _i < arguments.length; _i++) {
490
- runTimeArgs[_i] = arguments[_i];
491
- }
492
- return run(ctxRef, function () {
493
- return fn.apply(void 0, runTimeArgs);
494
- });
495
- };
496
- return returnedFn;
497
- }
498
- function use() {
499
- return storage.ctx;
500
- }
501
- function set(value) {
502
- return (storage.ctx = value);
503
- }
504
- function clear() {
505
- var _a;
506
- storage.ancestry.shift();
507
- set((_a = storage.ancestry[0]) !== null && _a !== void 0 ? _a : null);
508
- }
509
- }
510
-
511
- var ctx$1 = createContext(function (ctxRef, parentContext) {
512
- var base = {
513
- value: ctxRef.value,
514
- meta: ctxRef.meta || {}
515
- };
516
- if (!parentContext) {
517
- return assign(base, {
518
- parent: emptyParent
519
- });
520
- }
521
- else if (ctxRef.set) {
522
- return assign(base, {
523
- parent: function () { return stripContext(parentContext); }
524
- });
525
- }
526
- return parentContext;
527
- });
528
- function stripContext(ctx) {
529
- if (!ctx) {
530
- return ctx;
531
- }
532
- return {
533
- value: ctx.value,
534
- meta: ctx.meta,
535
- parent: ctx.parent
536
- };
537
- }
538
- function emptyParent() {
539
- return null;
540
- }
541
-
542
- function isArrayOf(inputArray, currentRule) {
543
- return defaultToPassing(mapFirst(inputArray, function (currentValue, breakout, index) {
544
- var res = ctx$1.run({ value: currentValue, set: true, meta: { index: index } }, function () { return runLazyRule(currentRule, currentValue); });
545
- if (!res.pass) {
546
- breakout(res);
547
- }
548
- }));
549
- }
550
-
551
- function loose(inputObject, shapeObject) {
552
- var _loop_1 = function (key) {
553
- var currentValue = inputObject[key];
554
- var currentRule = shapeObject[key];
555
- var res = ctx$1.run({ value: currentValue, set: true, meta: { key: key } }, function () {
556
- return runLazyRule(currentRule, currentValue);
557
- });
558
- if (!res.pass) {
559
- return { value: res };
560
- }
561
- };
562
- for (var key in shapeObject) {
563
- var state_1 = _loop_1(key);
564
- if (typeof state_1 === "object")
565
- return state_1.value;
566
- }
567
- return passing();
568
- }
569
-
570
- function shape(inputObject, shapeObject) {
571
- var baseRes = loose(inputObject, shapeObject);
572
- if (!baseRes.pass) {
573
- return baseRes;
574
- }
575
- for (var key in inputObject) {
576
- if (!hasOwnProperty(shapeObject, key)) {
577
- return failing();
578
- }
579
- }
580
- return passing();
581
- }
582
-
583
- function schema() {
584
- return { shape: shape, loose: loose, isArrayOf: isArrayOf };
585
- }
586
-
587
- var baseRules = assign(rules(), compounds(), schema());
588
- function getRule(ruleName) {
589
- return baseRules[ruleName];
590
- }
591
-
592
- function eachEnforceRule(action) {
593
- for (var ruleName in baseRules) {
594
- var ruleFn = getRule(ruleName);
595
- if (isFunction(ruleFn)) {
596
- action(ruleName, ruleFn);
597
- }
598
- }
599
- }
600
-
601
- function isProxySupported() {
602
- try {
603
- return typeof Proxy === 'function';
604
- }
605
- catch (_a) {
606
- return false;
607
- }
608
- }
609
-
610
- /**
611
- * Transform the result of a rule into a standard format
612
- */
613
- function transformResult(result, ruleName, value) {
614
- var args = [];
615
- for (var _i = 3; _i < arguments.length; _i++) {
616
- args[_i - 3] = arguments[_i];
617
- }
618
- validateResult(result);
619
- // if result is boolean
620
- if (isBoolean(result)) {
621
- return ruleReturn(result);
622
- }
623
- else {
624
- return ruleReturn(result.pass, optionalFunctionValue.apply(void 0, __spreadArray([result.message, ruleName, value], args)));
625
- }
626
- }
627
- function validateResult(result) {
628
- // if result is boolean, or if result.pass is boolean
629
- if (isBoolean(result) || (result && isBoolean(result.pass))) {
630
- return;
631
- }
632
- throwError('Incorrect return value for rule: ' + JSON.stringify(result));
633
- }
634
-
635
- function enforceEager(value) {
636
- var target = {};
637
- if (!isProxySupported()) {
638
- eachEnforceRule(function (ruleName, ruleFn) {
639
- target[ruleName] = genRuleCall(target, ruleFn, ruleName);
640
- });
641
- return target;
642
- }
643
- var proxy = new Proxy(target, {
644
- get: function (_, ruleName) {
645
- var rule = getRule(ruleName);
646
- if (rule) {
647
- return genRuleCall(proxy, rule, ruleName);
648
- }
649
- }
650
- });
651
- return proxy;
652
- function genRuleCall(target, rule, ruleName) {
653
- return function ruleCall() {
654
- var args = [];
655
- for (var _i = 0; _i < arguments.length; _i++) {
656
- args[_i] = arguments[_i];
657
- }
658
- var transformedResult = transformResult.apply(void 0, __spreadArray([ctx$1.run({ value: value }, function () { return rule.apply(void 0, __spreadArray([value], args)); }),
659
- ruleName,
660
- value], args));
661
- if (!transformedResult.pass) {
662
- if (isEmpty(transformedResult.message)) {
663
- throwError("enforce/" + ruleName + " failed with " + JSON.stringify(value));
664
- }
665
- else {
666
- // Explicitly throw a string so that vest.test can pick it up as the validation error message
667
- throw transformedResult.message;
668
- }
669
- }
670
- return target;
671
- };
672
- }
673
- }
674
-
675
- // eslint-disable-next-line max-lines-per-function
676
- function genEnforceLazy(key) {
677
- var registeredRules = [];
678
- var lazyMessage;
679
- return addLazyRule(key);
680
- // eslint-disable-next-line max-lines-per-function
681
- function addLazyRule(ruleName) {
682
- // eslint-disable-next-line max-lines-per-function
683
- return function () {
684
- var args = [];
685
- for (var _i = 0; _i < arguments.length; _i++) {
686
- args[_i] = arguments[_i];
687
- }
688
- var rule = getRule(ruleName);
689
- registeredRules.push(function (value) {
690
- return transformResult.apply(void 0, __spreadArray([rule.apply(void 0, __spreadArray([value], args)), ruleName, value], args));
691
- });
692
- var proxy = {
693
- run: function (value) {
694
- return defaultToPassing(mapFirst(registeredRules, function (rule, breakout) {
695
- var _a;
696
- var res = ctx$1.run({ value: value }, function () { return rule(value); });
697
- if (!res.pass) {
698
- breakout(ruleReturn(!!res.pass, (_a = optionalFunctionValue(lazyMessage, value, res.message)) !== null && _a !== void 0 ? _a : res.message));
699
- }
700
- }));
701
- },
702
- test: function (value) { return proxy.run(value).pass; },
703
- message: function (message) {
704
- if (message) {
705
- lazyMessage = message;
706
- }
707
- return proxy;
708
- }
709
- };
710
- if (!isProxySupported()) {
711
- eachEnforceRule(function (ruleName) {
712
- proxy[ruleName] = addLazyRule(ruleName);
713
- });
714
- return proxy;
715
- }
716
- // reassigning the proxy here is not pretty
717
- // but it's a cleaner way of getting `run` and `test` for free
718
- proxy = new Proxy(proxy, {
719
- get: function (target, key) {
720
- if (getRule(key)) {
721
- return addLazyRule(key);
722
- }
723
- return target[key]; // already has `run` and `test` on it
724
- }
725
- });
726
- return proxy;
727
- };
728
- }
729
- }
730
-
731
- // Help needed improving the typings of this file.
732
- // Ideally, we'd be able to extend TShapeObject, but that's not possible.
733
- function partial(shapeObject) {
734
- var output = {};
735
- for (var key in shapeObject) {
736
- output[key] = enforce.optional(shapeObject[key]);
737
- }
738
- return output;
739
- }
740
-
741
- function modifiers() {
742
- return { partial: partial };
743
- }
744
-
745
- /**
746
- * Enforce is quite complicated, I want to explain it in detail.
747
- * It is dynamic in nature, so a lot of proxy objects are involved.
748
- *
749
- * Enforce has two main interfaces
750
- * 1. eager
751
- * 2. lazy
752
- *
753
- * The eager interface is the most commonly used, and the easier to understand.
754
- * It throws an error when a rule is not satisfied.
755
- * The eager interface is declared in enforceEager.ts and it is quite simple to understand.
756
- * enforce is called with a value, and the return value is a proxy object that points back to all the rules.
757
- * When a rule is called, the value is mapped as its first argument, and if the rule passes, the same
758
- * proxy object is returned. Otherwise, an error is thrown.
759
- *
760
- * The lazy interface works quite differently. It is declared in genEnforceLazy.ts.
761
- * Rather than calling enforce directly, the lazy interface has all the rules as "methods" (only by proxy).
762
- * Calling the first function in the chain will initialize an array of calls. It stores the different rule calls
763
- * and the parameters passed to them. None of the rules are called yet.
764
- * The rules are only invoked in sequence once either of these chained functions are called:
765
- * 1. test(value)
766
- * 2. run(value)
767
- *
768
- * Calling run or test will call all the rules in sequence, with the difference that test will only return a boolean value,
769
- * while run will return an object with the validation result and an optional message created by the rule.
770
- */
771
- function genEnforce() {
772
- var target = __assign({ context: function () { return ctx$1.useX(); }, extend: function (customRules) {
773
- assign(baseRules, customRules);
774
- } }, modifiers());
775
- if (!isProxySupported()) {
776
- eachEnforceRule(function (ruleName) {
777
- // Only on the first rule access - start the chain of calls
778
- target[ruleName] = genEnforceLazy(ruleName);
779
- });
780
- return target;
781
- }
782
- return new Proxy(assign(enforceEager, target), {
783
- get: function (target, key) {
784
- if (key in target) {
785
- return target[key];
786
- }
787
- if (!getRule(key)) {
788
- return;
789
- }
790
- // Only on the first rule access - start the chain of calls
791
- return genEnforceLazy(key);
792
- }
793
- });
794
- }
795
- var enforce = genEnforce();
796
-
797
- /**
798
- * @returns a unique numeric id.
799
- */
800
- var genId = (function (n) { return function () {
801
- return "" + n++;
802
- }; })(0);
803
-
804
46
  // eslint-disable-next-line max-lines-per-function
805
47
  function createState(onStateChange) {
806
48
  var state = {
@@ -875,17 +117,18 @@ var IsolateTypes;
875
117
  })(IsolateTypes || (IsolateTypes = {}));
876
118
 
877
119
  function createStateRef(state, _a) {
878
- var suiteId = _a.suiteId;
120
+ var suiteId = _a.suiteId, suiteName = _a.suiteName;
879
121
  return {
880
122
  optionalFields: state.registerStateKey(function () { return ({}); }),
881
123
  suiteId: state.registerStateKey(suiteId),
124
+ suiteName: state.registerStateKey(suiteName),
882
125
  testCallbacks: state.registerStateKey(function () { return ({
883
126
  fieldCallbacks: {},
884
127
  doneCallbacks: []
885
128
  }); }),
886
129
  testObjects: state.registerStateKey(function (prev) {
887
130
  return {
888
- prev: (prev ? prev.current : []),
131
+ prev: prev ? prev.current : [],
889
132
  current: []
890
133
  };
891
134
  })
@@ -898,8 +141,7 @@ function asArray(possibleArg) {
898
141
 
899
142
  function last(values) {
900
143
  var valuesArray = asArray(values);
901
- var _a = valuesArray, l = _a.length, _b = l - 1, lastValue = _a[_b];
902
- return lastValue;
144
+ return valuesArray[valuesArray.length - 1];
903
145
  }
904
146
 
905
147
  function createCursor() {
@@ -936,19 +178,47 @@ function createCursor() {
936
178
  };
937
179
  }
938
180
 
939
- var ctx = createContext(function (ctxRef, parentContext) {
181
+ var context = context$1.createContext(function (ctxRef, parentContext) {
940
182
  return parentContext
941
183
  ? null
942
184
  : assign({}, {
943
- isolate: { type: IsolateTypes.DEFAULT },
944
- testCursor: createCursor(),
945
185
  exclusion: {
946
186
  tests: {},
947
187
  groups: {}
948
- }
188
+ },
189
+ isolate: {
190
+ type: IsolateTypes.DEFAULT,
191
+ keys: {
192
+ current: {},
193
+ prev: {}
194
+ }
195
+ },
196
+ testCursor: createCursor()
949
197
  }, ctxRef);
950
198
  });
951
199
 
200
+ function bindNot(fn) {
201
+ return function () {
202
+ var args = [];
203
+ for (var _i = 0; _i < arguments.length; _i++) {
204
+ args[_i] = arguments[_i];
205
+ }
206
+ return !fn.apply(void 0, args);
207
+ };
208
+ }
209
+
210
+ // The module is named "isArrayValue" since it
211
+ // is conflicting with a nested npm dependency.
212
+ // We may need to revisit this in the future.
213
+ function isArray(value) {
214
+ return Boolean(Array.isArray(value));
215
+ }
216
+
217
+ function isNull(value) {
218
+ return value === null;
219
+ }
220
+ var isNotNull = bindNot(isNull);
221
+
952
222
  // This is sort of a map/filter in one function.
953
223
  // Normally, behaves like a nested-array map
954
224
  // Returning `null` will drop the element from the array
@@ -994,6 +264,31 @@ function getCurrent(array, path) {
994
264
  return current;
995
265
  }
996
266
 
267
+ function isUndefined(value) {
268
+ return value === undefined;
269
+ }
270
+
271
+ function isNullish(value) {
272
+ return isNull(value) || isUndefined(value);
273
+ }
274
+
275
+ function isStringValue(v) {
276
+ return String(v) === v;
277
+ }
278
+
279
+ function shouldUseErrorAsMessage(message, error) {
280
+ // kind of cheating with this safe guard, but it does the job
281
+ return isUndefined(message) && isStringValue(error);
282
+ }
283
+
284
+ function lengthEquals(value, arg1) {
285
+ return value.length === Number(arg1);
286
+ }
287
+
288
+ function longerThan(value, arg1) {
289
+ return value.length > Number(arg1);
290
+ }
291
+
997
292
  /**
998
293
  * Creates a cache function
999
294
  */
@@ -1035,11 +330,14 @@ function createCache(maxSize) {
1035
330
  // STATE REF
1036
331
  function useStateRef() {
1037
332
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1038
- return ctx.useX().stateRef; // I should revisit this
333
+ return context.useX().stateRef; // I should revisit this
1039
334
  }
1040
335
  // STATE KEYS
1041
336
  function useSuiteId() {
1042
- return useStateRef().suiteId();
337
+ return useStateRef().suiteId()[0];
338
+ }
339
+ function useSuiteName() {
340
+ return useStateRef().suiteName()[0];
1043
341
  }
1044
342
  function useTestCallbacks() {
1045
343
  return useStateRef().testCallbacks();
@@ -1096,25 +394,187 @@ function useTestsFlat() {
1096
394
  return flatCache([current], function () { return flatten(current); });
1097
395
  }
1098
396
 
397
+ var TestSeverity;
398
+ (function (TestSeverity) {
399
+ TestSeverity["Error"] = "error";
400
+ TestSeverity["Warning"] = "warning";
401
+ })(TestSeverity || (TestSeverity = {}));
402
+ var VestTest = /** @class */ (function () {
403
+ function VestTest(fieldName, testFn, _a) {
404
+ var _b = _a === void 0 ? {} : _a, message = _b.message, groupName = _b.groupName, key = _b.key;
405
+ this.key = null;
406
+ this.id = genId();
407
+ this.severity = TestSeverity.Error;
408
+ this.status = STATUS_UNTESTED;
409
+ this.fieldName = fieldName;
410
+ this.testFn = testFn;
411
+ if (groupName) {
412
+ this.groupName = groupName;
413
+ }
414
+ if (message) {
415
+ this.message = message;
416
+ }
417
+ if (key) {
418
+ this.key = key;
419
+ }
420
+ }
421
+ VestTest.prototype.run = function () {
422
+ var result;
423
+ try {
424
+ result = this.testFn();
425
+ }
426
+ catch (error) {
427
+ if (shouldUseErrorAsMessage(this.message, error)) {
428
+ this.message = error;
429
+ }
430
+ result = false;
431
+ }
432
+ if (result === false) {
433
+ this.fail();
434
+ }
435
+ return result;
436
+ };
437
+ VestTest.prototype.setStatus = function (status) {
438
+ if (this.isFinalStatus() && status !== STATUS_OMITTED) {
439
+ return;
440
+ }
441
+ this.status = status;
442
+ };
443
+ VestTest.prototype.warns = function () {
444
+ return this.severity === TestSeverity.Warning;
445
+ };
446
+ VestTest.prototype.setPending = function () {
447
+ this.setStatus(STATUS_PENDING);
448
+ };
449
+ VestTest.prototype.fail = function () {
450
+ this.setStatus(this.warns() ? STATUS_WARNING : STATUS_FAILED);
451
+ };
452
+ VestTest.prototype.done = function () {
453
+ if (this.isFinalStatus()) {
454
+ return;
455
+ }
456
+ this.setStatus(STATUS_PASSING);
457
+ };
458
+ VestTest.prototype.warn = function () {
459
+ this.severity = TestSeverity.Warning;
460
+ };
461
+ VestTest.prototype.isFinalStatus = function () {
462
+ return this.hasFailures() || this.isCanceled() || this.isPassing();
463
+ };
464
+ VestTest.prototype.skip = function (force) {
465
+ if (this.isPending() && !force) {
466
+ // Without this condition, the test will be marked as skipped even if it is pending.
467
+ // This means that it will not be counted in "allIncomplete" and its done callbacks
468
+ // will not be called, or will be called prematurely.
469
+ // What this mostly say is that when we have a pending test for one field, and we then
470
+ // start typing in a different field - the pending test will be canceled, which
471
+ // is usually an unwanted behavior.
472
+ // The only scenario in which we DO want to cancel the async test regardless
473
+ // is when we specifically skip a test with `skipWhen`, which is handled by the
474
+ // "force" boolean flag.
475
+ // I am not a fan of this flag, but it gets the job done.
476
+ return;
477
+ }
478
+ this.setStatus(STATUS_SKIPPED);
479
+ };
480
+ VestTest.prototype.cancel = function () {
481
+ this.setStatus(STATUS_CANCELED);
482
+ useRefreshTestObjects();
483
+ };
484
+ VestTest.prototype.omit = function () {
485
+ this.setStatus(STATUS_OMITTED);
486
+ };
487
+ VestTest.prototype.valueOf = function () {
488
+ return !this.isFailing();
489
+ };
490
+ VestTest.prototype.hasFailures = function () {
491
+ return this.isFailing() || this.isWarning();
492
+ };
493
+ VestTest.prototype.isPending = function () {
494
+ return this.status === STATUS_PENDING;
495
+ };
496
+ VestTest.prototype.isTested = function () {
497
+ return this.hasFailures() || this.isPassing();
498
+ };
499
+ VestTest.prototype.isOmitted = function () {
500
+ return this.status === STATUS_OMITTED;
501
+ };
502
+ VestTest.prototype.isUntested = function () {
503
+ return this.status === STATUS_UNTESTED;
504
+ };
505
+ VestTest.prototype.isFailing = function () {
506
+ return this.status === STATUS_FAILED;
507
+ };
508
+ VestTest.prototype.isCanceled = function () {
509
+ return this.status === STATUS_CANCELED;
510
+ };
511
+ VestTest.prototype.isSkipped = function () {
512
+ return this.status === STATUS_SKIPPED;
513
+ };
514
+ VestTest.prototype.isPassing = function () {
515
+ return this.status === STATUS_PASSING;
516
+ };
517
+ VestTest.prototype.isWarning = function () {
518
+ return this.status === STATUS_WARNING;
519
+ };
520
+ return VestTest;
521
+ }());
522
+ var STATUS_UNTESTED = 'UNTESTED';
523
+ var STATUS_SKIPPED = 'SKIPPED';
524
+ var STATUS_FAILED = 'FAILED';
525
+ var STATUS_WARNING = 'WARNING';
526
+ var STATUS_PASSING = 'PASSING';
527
+ var STATUS_PENDING = 'PENDING';
528
+ var STATUS_CANCELED = 'CANCELED';
529
+ var STATUS_OMITTED = 'OMITTED';
530
+
1099
531
  function usePath() {
1100
- var context = ctx.useX();
1101
- return context.testCursor.getCursor();
532
+ var context$1 = context.useX();
533
+ return context$1.testCursor.getCursor();
1102
534
  }
1103
535
  function useCursorAt() {
1104
- var context = ctx.useX();
1105
- return context.testCursor.cursorAt();
536
+ var context$1 = context.useX();
537
+ return context$1.testCursor.cursorAt();
1106
538
  }
1107
539
  function moveForward() {
1108
- var context = ctx.useX();
1109
- return context.testCursor.next();
540
+ var context$1 = context.useX();
541
+ return context$1.testCursor.next();
1110
542
  }
1111
543
  function addLevel() {
1112
- var context = ctx.useX();
1113
- context.testCursor.addLevel();
544
+ var context$1 = context.useX();
545
+ context$1.testCursor.addLevel();
1114
546
  }
1115
547
  function removeLevel() {
1116
- var context = ctx.useX();
1117
- context.testCursor.removeLevel();
548
+ var context$1 = context.useX();
549
+ context$1.testCursor.removeLevel();
550
+ }
551
+
552
+ function usePrevKeys() {
553
+ var prev = useTestObjects()[0].prev;
554
+ return asArray(getCurrent(prev, usePath())).reduce(function (prevKeys, testObject) {
555
+ if (!(testObject instanceof VestTest)) {
556
+ return prevKeys;
557
+ }
558
+ if (isNullish(testObject.key)) {
559
+ return prevKeys;
560
+ }
561
+ prevKeys[testObject.key] = testObject;
562
+ return prevKeys;
563
+ }, {});
564
+ }
565
+ function usePrevTestByKey(key) {
566
+ var prev = context.useX().isolate.keys.prev;
567
+ return prev[key];
568
+ }
569
+ function useRetainTestKey(key, testObject) {
570
+ var context$1 = context.useX();
571
+ var current = context$1.isolate.keys.current;
572
+ if (isNullish(current[key])) {
573
+ current[key] = testObject;
574
+ }
575
+ else {
576
+ throwErrorDeferred("Encountered the same test key \"" + key + "\" twice. This may lead to tests overriding each other's results, or to tests being unexpectedly omitted.");
577
+ }
1118
578
  }
1119
579
 
1120
580
  function isolate(_a, callback) {
@@ -1122,9 +582,14 @@ function isolate(_a, callback) {
1122
582
  if (!isFunction(callback)) {
1123
583
  return;
1124
584
  }
585
+ var keys = {
586
+ current: {},
587
+ prev: {}
588
+ };
1125
589
  var path = usePath();
1126
- return ctx.run({ isolate: { type: type } }, function () {
590
+ return context.run({ isolate: { type: type, keys: keys } }, function () {
1127
591
  addLevel();
592
+ keys.prev = usePrevKeys();
1128
593
  useSetTests(function (tests) { return setValueAtPath(tests, path, []); });
1129
594
  var res = callback();
1130
595
  removeLevel();
@@ -1133,43 +598,42 @@ function isolate(_a, callback) {
1133
598
  });
1134
599
  }
1135
600
  function shouldAllowReorder() {
1136
- return ctx.useX().isolate.type === IsolateTypes.EACH;
601
+ return context.useX().isolate.type === IsolateTypes.EACH;
1137
602
  }
1138
603
 
1139
- function nonMatchingFieldName(testObject, fieldName) {
1140
- return !!fieldName && !matchingFieldName(testObject, fieldName);
604
+ /**
605
+ * A safe hasOwnProperty access
606
+ */
607
+ function hasOwnProperty(obj, key) {
608
+ return Object.prototype.hasOwnProperty.call(obj, key);
1141
609
  }
1142
- function matchingFieldName(testObject, fieldName) {
1143
- return !!(fieldName && testObject.fieldName === fieldName);
610
+
611
+ function isNumber(value) {
612
+ return Boolean(typeof value === 'number');
1144
613
  }
1145
614
 
1146
- function omitOptionalTests() {
1147
- var optionalFields = useOptionalFields()[0];
1148
- if (isEmpty(optionalFields)) {
1149
- return;
615
+ function isEmpty(value) {
616
+ if (!value) {
617
+ return true;
1150
618
  }
1151
- var shouldOmit = {};
1152
- useSetTests(function (tests) {
1153
- return transform(tests, function (testObject) {
1154
- var fieldName = testObject.fieldName;
1155
- if (shouldOmit.hasOwnProperty(fieldName)) {
1156
- omit(testObject);
1157
- }
1158
- else {
1159
- var optionalConfig = optionalFields[fieldName];
1160
- if (isFunction(optionalConfig)) {
1161
- shouldOmit[fieldName] = optionalConfig();
1162
- omit(testObject);
1163
- }
1164
- }
1165
- return testObject;
1166
- });
1167
- });
1168
- function omit(testObject) {
1169
- if (shouldOmit[testObject.fieldName]) {
1170
- testObject.omit();
1171
- }
619
+ else if (isNumber(value)) {
620
+ return value === 0;
621
+ }
622
+ else if (hasOwnProperty(value, 'length')) {
623
+ return lengthEquals(value, 0);
1172
624
  }
625
+ else if (typeof value === 'object') {
626
+ return lengthEquals(Object.keys(value), 0);
627
+ }
628
+ return true;
629
+ }
630
+ var isNotEmpty = bindNot(isEmpty);
631
+
632
+ function nonMatchingFieldName(testObject, fieldName) {
633
+ return !!fieldName && !matchingFieldName(testObject, fieldName);
634
+ }
635
+ function matchingFieldName(testObject, fieldName) {
636
+ return !!(fieldName && testObject.fieldName === fieldName);
1173
637
  }
1174
638
 
1175
639
  /**
@@ -1252,6 +716,42 @@ function genTestObject(summaryKey, testObject) {
1252
716
  return testKey;
1253
717
  }
1254
718
 
719
+ /*! *****************************************************************************
720
+ Copyright (c) Microsoft Corporation.
721
+
722
+ Permission to use, copy, modify, and/or distribute this software for any
723
+ purpose with or without fee is hereby granted.
724
+
725
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
726
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
727
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
728
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
729
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
730
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
731
+ PERFORMANCE OF THIS SOFTWARE.
732
+ ***************************************************************************** */
733
+
734
+ var __assign = function() {
735
+ __assign = Object.assign || function __assign(t) {
736
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
737
+ s = arguments[i];
738
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
739
+ }
740
+ return t;
741
+ };
742
+ return __assign.apply(this, arguments);
743
+ };
744
+
745
+ function __spreadArray(to, from, pack) {
746
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
747
+ if (ar || !(i in from)) {
748
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
749
+ ar[i] = from[i];
750
+ }
751
+ }
752
+ return to.concat(ar || from);
753
+ }
754
+
1255
755
  function either(a, b) {
1256
756
  return !!a !== !!b;
1257
757
  }
@@ -1260,7 +760,7 @@ function either(a, b) {
1260
760
  * Checks that a given test object matches the currently specified severity level
1261
761
  */
1262
762
  function nonMatchingSeverityProfile(severity, testObject) {
1263
- return either(severity === 'warnings', testObject.warns);
763
+ return either(severity === 'warnings', testObject.warns());
1264
764
  }
1265
765
 
1266
766
  function collectFailureMessages(severity, testObjects, options) {
@@ -1445,19 +945,21 @@ var cache$1 = createCache(20);
1445
945
  function produceDraft() {
1446
946
  var testObjects = useTestsFlat();
1447
947
  var ctxRef = { stateRef: useStateRef() };
1448
- return cache$1([testObjects], ctx.bind(ctxRef, function () {
948
+ return cache$1([testObjects], context.bind(ctxRef, function () {
949
+ var suiteName = useSuiteName();
1449
950
  return assign(genTestsSummary(), {
1450
- getErrors: ctx.bind(ctxRef, getErrors),
1451
- getErrorsByGroup: ctx.bind(ctxRef, getErrorsByGroup),
1452
- getWarnings: ctx.bind(ctxRef, getWarnings),
1453
- getWarningsByGroup: ctx.bind(ctxRef, getWarningsByGroup),
1454
- hasErrors: ctx.bind(ctxRef, hasErrors),
1455
- hasErrorsByGroup: ctx.bind(ctxRef, hasErrorsByGroup),
1456
- hasWarnings: ctx.bind(ctxRef, hasWarnings),
1457
- hasWarningsByGroup: ctx.bind(ctxRef, hasWarningsByGroup),
1458
- isValid: ctx.bind(ctxRef, function (fieldName) {
951
+ getErrors: context.bind(ctxRef, getErrors),
952
+ getErrorsByGroup: context.bind(ctxRef, getErrorsByGroup),
953
+ getWarnings: context.bind(ctxRef, getWarnings),
954
+ getWarningsByGroup: context.bind(ctxRef, getWarningsByGroup),
955
+ hasErrors: context.bind(ctxRef, hasErrors),
956
+ hasErrorsByGroup: context.bind(ctxRef, hasErrorsByGroup),
957
+ hasWarnings: context.bind(ctxRef, hasWarnings),
958
+ hasWarningsByGroup: context.bind(ctxRef, hasWarningsByGroup),
959
+ isValid: context.bind(ctxRef, function (fieldName) {
1459
960
  return isValid(produceDraft(), fieldName);
1460
- })
961
+ }),
962
+ suiteName: suiteName
1461
963
  });
1462
964
  }));
1463
965
  }
@@ -1466,9 +968,9 @@ var cache = createCache(20);
1466
968
  function produceFullResult() {
1467
969
  var testObjects = useTestsFlat();
1468
970
  var ctxRef = { stateRef: useStateRef() };
1469
- return cache([testObjects], ctx.bind(ctxRef, function () {
971
+ return cache([testObjects], context.bind(ctxRef, function () {
1470
972
  return assign({}, produceDraft(), {
1471
- done: ctx.bind(ctxRef, done)
973
+ done: context.bind(ctxRef, done)
1472
974
  });
1473
975
  }));
1474
976
  }
@@ -1479,7 +981,7 @@ function shouldSkipDoneRegistration(callback, fieldName, output) {
1479
981
  // If we do not have any test runs for the current field
1480
982
  return !!(!isFunction(callback) ||
1481
983
  (fieldName &&
1482
- (!output.tests[fieldName] || output.tests[fieldName].testCount === 0)));
984
+ (!output.tests[fieldName] || isEmpty(output.tests[fieldName].testCount))));
1483
985
  }
1484
986
  function shouldRunDoneCallback(fieldName) {
1485
987
  // is suite finished || field name exists, and test is finished;
@@ -1509,25 +1011,16 @@ var done = function done() {
1509
1011
  return output;
1510
1012
  };
1511
1013
  function deferDoneCallback(doneCallback, fieldName) {
1512
- var deferredCallback = ctx.bind({}, doneCallback);
1014
+ var deferredCallback = context.bind({}, doneCallback);
1513
1015
  var _a = useTestCallbacks(), setTestCallbacks = _a[1];
1514
1016
  setTestCallbacks(function (current) {
1515
- if (fieldName) {
1516
- current.fieldCallbacks[fieldName] = (current.fieldCallbacks[fieldName] || []).concat(deferredCallback);
1517
- }
1518
- else {
1519
- current.doneCallbacks.push(deferredCallback);
1520
- }
1521
- return current;
1522
- });
1523
- }
1524
-
1525
- /**
1526
- * Removes test object from suite state
1527
- */
1528
- function removeTestFromState (testObject) {
1529
- useSetTests(function (tests) {
1530
- return transform(tests, function (test) { return (testObject !== test ? test : null); });
1017
+ if (fieldName) {
1018
+ current.fieldCallbacks[fieldName] = (current.fieldCallbacks[fieldName] || []).concat(deferredCallback);
1019
+ }
1020
+ else {
1021
+ current.doneCallbacks.push(deferredCallback);
1022
+ }
1023
+ return current;
1531
1024
  });
1532
1025
  }
1533
1026
 
@@ -1556,6 +1049,44 @@ function createBus() {
1556
1049
  };
1557
1050
  }
1558
1051
 
1052
+ function omitOptionalTests() {
1053
+ var optionalFields = useOptionalFields()[0];
1054
+ if (isEmpty(optionalFields)) {
1055
+ return;
1056
+ }
1057
+ var shouldOmit = {};
1058
+ useSetTests(function (tests) {
1059
+ return transform(tests, function (testObject) {
1060
+ var fieldName = testObject.fieldName;
1061
+ if (shouldOmit.hasOwnProperty(fieldName)) {
1062
+ omit(testObject);
1063
+ }
1064
+ else {
1065
+ var optionalConfig = optionalFields[fieldName];
1066
+ if (isFunction(optionalConfig)) {
1067
+ shouldOmit[fieldName] = optionalConfig();
1068
+ omit(testObject);
1069
+ }
1070
+ }
1071
+ return testObject;
1072
+ });
1073
+ });
1074
+ function omit(testObject) {
1075
+ if (shouldOmit[testObject.fieldName]) {
1076
+ testObject.omit();
1077
+ }
1078
+ }
1079
+ }
1080
+
1081
+ /**
1082
+ * Removes test object from suite state
1083
+ */
1084
+ function removeTestFromState (testObject) {
1085
+ useSetTests(function (tests) {
1086
+ return transform(tests, function (test) { return (testObject !== test ? test : null); });
1087
+ });
1088
+ }
1089
+
1559
1090
  function callEach(arr) {
1560
1091
  return arr.forEach(function (fn) { return fn(); });
1561
1092
  }
@@ -1583,6 +1114,8 @@ function runDoneCallbacks() {
1583
1114
 
1584
1115
  function initBus() {
1585
1116
  var bus = createBus();
1117
+ // Report a the completion of a test. There may be other tests with the same
1118
+ // name that are still running, or not yet started.
1586
1119
  bus.on(Events.TEST_COMPLETED, function (testObject) {
1587
1120
  if (testObject.isCanceled()) {
1588
1121
  return;
@@ -1591,58 +1124,96 @@ function initBus() {
1591
1124
  runFieldCallbacks(testObject.fieldName);
1592
1125
  runDoneCallbacks();
1593
1126
  });
1127
+ // Report that the suite completed its synchronous test run.
1128
+ // Async operations may still be running.
1129
+ bus.on(Events.SUITE_COMPLETED, function () {
1130
+ // Remove tests that are optional and need to be omitted
1131
+ omitOptionalTests();
1132
+ });
1133
+ // Removes a certain field from the state.
1134
+ bus.on(Events.REMOVE_FIELD, function (fieldName) {
1135
+ var testObjects = useTestsFlat();
1136
+ testObjects.forEach(function (testObject) {
1137
+ if (matchingFieldName(testObject, fieldName)) {
1138
+ testObject.cancel();
1139
+ removeTestFromState(testObject);
1140
+ }
1141
+ });
1142
+ });
1594
1143
  return bus;
1595
1144
  }
1596
1145
  function useBus() {
1597
- var context = ctx.useX();
1598
- if (!context.bus) {
1146
+ var context$1 = context.useX();
1147
+ if (!context$1.bus) {
1599
1148
  throwError();
1600
1149
  }
1601
- return context.bus;
1150
+ return context$1.bus;
1602
1151
  }
1603
1152
  var Events;
1604
1153
  (function (Events) {
1605
1154
  Events["TEST_COMPLETED"] = "test_completed";
1155
+ Events["REMOVE_FIELD"] = "remove_field";
1156
+ Events["SUITE_COMPLETED"] = "suite_completed";
1606
1157
  })(Events || (Events = {}));
1607
1158
 
1608
1159
  // eslint-disable-next-line max-lines-per-function
1609
- function create(suiteCallback) {
1160
+ function create() {
1161
+ var args = [];
1162
+ for (var _i = 0; _i < arguments.length; _i++) {
1163
+ args[_i] = arguments[_i];
1164
+ }
1165
+ var _a = args.reverse(), suiteCallback = _a[0], suiteName = _a[1];
1610
1166
  if (!isFunction(suiteCallback)) {
1611
- throwError('Suite initialization error. Expected `tests` to be a function.');
1167
+ throwError('vest.create: Expected callback to be a function.');
1612
1168
  }
1169
+ // Event bus initialization
1613
1170
  var bus = initBus();
1171
+ // State initialization
1614
1172
  var state = createState();
1615
- var stateRef = createStateRef(state, { suiteId: genId() });
1616
- var suite = assign(ctx.bind({ stateRef: stateRef, bus: bus }, function () {
1173
+ // State reference - this holds the actual state values
1174
+ var stateRef = createStateRef(state, { suiteId: genId(), suiteName: suiteName });
1175
+ // Create base context reference. All hooks will derive their data from this
1176
+ var ctxRef = { stateRef: stateRef, bus: bus };
1177
+ var suite = assign(
1178
+ // Bind the suite body to the context
1179
+ context.bind(ctxRef, function () {
1617
1180
  var args = [];
1618
1181
  for (var _i = 0; _i < arguments.length; _i++) {
1619
1182
  args[_i] = arguments[_i];
1620
1183
  }
1184
+ // Reset the state. Migrates current test objects to `prev` array.
1621
1185
  state.reset();
1622
- // Run the consumer's callback
1186
+ // Create a top level isolate
1623
1187
  isolate({ type: IsolateTypes.SUITE }, function () {
1188
+ // Run the consumer's callback
1624
1189
  suiteCallback.apply(void 0, args);
1625
1190
  });
1626
- omitOptionalTests();
1627
- var res = produceFullResult();
1628
- return res;
1191
+ // Report the suite is done registering tests
1192
+ // Async tests may still be running
1193
+ bus.emit(Events.SUITE_COMPLETED);
1194
+ // Return the result
1195
+ return produceFullResult();
1629
1196
  }), {
1630
- get: ctx.bind({ stateRef: stateRef }, produceDraft),
1631
- remove: ctx.bind({ stateRef: stateRef }, function (name) {
1632
- var testObjects = useTestsFlat();
1633
- // We're mutating the array in `cancel`, so we have to first copy it.
1634
- testObjects.forEach(function (testObject) {
1635
- if (matchingFieldName(testObject, name)) {
1636
- testObject.cancel();
1637
- removeTestFromState(testObject);
1638
- }
1639
- });
1640
- }),
1641
- reset: state.reset
1197
+ get: context.bind(ctxRef, produceDraft),
1198
+ reset: state.reset,
1199
+ remove: context.bind(ctxRef, function (fieldName) {
1200
+ bus.emit(Events.REMOVE_FIELD, fieldName);
1201
+ })
1642
1202
  });
1643
1203
  return suite;
1644
1204
  }
1645
1205
 
1206
+ function each(list, callback) {
1207
+ if (!isFunction(callback)) {
1208
+ throwError('callback must be a function');
1209
+ }
1210
+ isolate({ type: IsolateTypes.EACH }, function () {
1211
+ list.forEach(function (arg, index) {
1212
+ callback(arg, index);
1213
+ });
1214
+ });
1215
+ }
1216
+
1646
1217
  /**
1647
1218
  * Error message to display when a hook was called outside of context.
1648
1219
  */
@@ -1652,24 +1223,31 @@ var ERROR_HOOK_CALLED_OUTSIDE = 'hook called outside of a running suite.';
1652
1223
  * Adds a field or multiple fields to inclusion group.
1653
1224
  */
1654
1225
  function only(item) {
1655
- return addTo('only', 'tests', item);
1226
+ return addTo(0 /* ONLY */, 'tests', item);
1656
1227
  }
1657
- only.group = function (item) { return addTo('only', 'groups', item); };
1228
+ only.group = function (item) {
1229
+ return addTo(0 /* ONLY */, 'groups', item);
1230
+ };
1658
1231
  /**
1659
1232
  * Adds a field or multiple fields to exclusion group.
1660
1233
  */
1661
1234
  function skip(item) {
1662
- return addTo('skip', 'tests', item);
1235
+ return addTo(1 /* SKIP */, 'tests', item);
1236
+ }
1237
+ skip.group = function (item) {
1238
+ return addTo(1 /* SKIP */, 'groups', item);
1239
+ };
1240
+ function isExcludedIndividually() {
1241
+ return !!context.useX().skipped;
1663
1242
  }
1664
- skip.group = function (item) { return addTo('skip', 'groups', item); };
1665
1243
  //Checks whether a certain test profile excluded by any of the exclusion groups.
1666
1244
  // eslint-disable-next-line complexity, max-statements
1667
1245
  function isExcluded(testObject) {
1668
1246
  var fieldName = testObject.fieldName, groupName = testObject.groupName;
1669
- var context = ctx.useX();
1670
- if (context.skipped)
1247
+ if (isExcludedIndividually())
1671
1248
  return true;
1672
- var exclusion = context.exclusion;
1249
+ var context$1 = context.useX();
1250
+ var exclusion = context$1.exclusion;
1673
1251
  var keyTests = exclusion.tests;
1674
1252
  var testValue = keyTests[fieldName];
1675
1253
  // if test is skipped
@@ -1692,6 +1270,9 @@ function isExcluded(testObject) {
1692
1270
  return keyTests[fieldName] === false;
1693
1271
  }
1694
1272
  }
1273
+ if (isMissingFromIncludedGroup(groupName)) {
1274
+ return true;
1275
+ }
1695
1276
  // if field is only'ed
1696
1277
  if (isTestIncluded)
1697
1278
  return false;
@@ -1699,12 +1280,40 @@ function isExcluded(testObject) {
1699
1280
  // Otherwise return false
1700
1281
  return hasIncludedTests(keyTests);
1701
1282
  }
1283
+ // eslint-disable-next-line max-statements
1284
+ function isMissingFromIncludedGroup(groupName) {
1285
+ var context$1 = context.useX();
1286
+ var exclusion = context$1.exclusion;
1287
+ if (!hasIncludedGroups()) {
1288
+ return false;
1289
+ }
1290
+ if (!groupName) {
1291
+ return true;
1292
+ }
1293
+ if (groupName in exclusion.groups) {
1294
+ if (exclusion.groups[groupName]) {
1295
+ return false;
1296
+ }
1297
+ return true;
1298
+ }
1299
+ return true;
1300
+ }
1301
+ function hasIncludedGroups() {
1302
+ var context$1 = context.useX();
1303
+ var exclusion = context$1.exclusion;
1304
+ for (var group in exclusion.groups) {
1305
+ if (exclusion.groups[group]) {
1306
+ return true;
1307
+ }
1308
+ }
1309
+ return false;
1310
+ }
1702
1311
  /**
1703
1312
  * Checks whether a given group is excluded from running.
1704
1313
  */
1705
1314
  function isGroupExcluded(groupName) {
1706
- var context = ctx.useX();
1707
- var exclusion = context.exclusion;
1315
+ var context$1 = context.useX();
1316
+ var exclusion = context$1.exclusion;
1708
1317
  var keyGroups = exclusion.groups;
1709
1318
  var groupPresent = hasOwnProperty(keyGroups, groupName);
1710
1319
  // When group is either only'ed or skipped
@@ -1725,7 +1334,7 @@ function isGroupExcluded(groupName) {
1725
1334
  * Adds fields to a specified exclusion group.
1726
1335
  */
1727
1336
  function addTo(exclusionGroup, itemType, item) {
1728
- var context = ctx.useX(ERROR_HOOK_CALLED_OUTSIDE);
1337
+ var context$1 = context.useX(ERROR_HOOK_CALLED_OUTSIDE);
1729
1338
  if (!item) {
1730
1339
  return;
1731
1340
  }
@@ -1733,7 +1342,8 @@ function addTo(exclusionGroup, itemType, item) {
1733
1342
  if (!isStringValue(itemName)) {
1734
1343
  return;
1735
1344
  }
1736
- context.exclusion[itemType][itemName] = exclusionGroup === 'only';
1345
+ context$1.exclusion[itemType][itemName] =
1346
+ exclusionGroup === 0 /* ONLY */;
1737
1347
  });
1738
1348
  }
1739
1349
  /**
@@ -1748,25 +1358,6 @@ function hasIncludedTests(keyTests) {
1748
1358
  return false;
1749
1359
  }
1750
1360
 
1751
- function skipWhen(conditional, callback) {
1752
- isolate({ type: IsolateTypes.SKIP_WHEN }, function () {
1753
- ctx.run({ skipped: optionalFunctionValue(conditional) }, function () { return callback(); });
1754
- });
1755
- }
1756
-
1757
- var ERROR_OUTSIDE_OF_TEST = "warn hook called outside of a test callback. It won't have an effect."
1758
- ;
1759
- /**
1760
- * Sets a running test to warn only mode.
1761
- */
1762
- function warn() {
1763
- var ctx$1 = ctx.useX('warn ' + ERROR_HOOK_CALLED_OUTSIDE);
1764
- if (!ctx$1.currentTest) {
1765
- throwError(ERROR_OUTSIDE_OF_TEST);
1766
- }
1767
- ctx$1.currentTest.warn();
1768
- }
1769
-
1770
1361
  /**
1771
1362
  * Runs a group callback.
1772
1363
  */
@@ -1779,7 +1370,7 @@ function group(groupName, tests) {
1779
1370
  }
1780
1371
  // Running with the context applied
1781
1372
  isolate({ type: IsolateTypes.GROUP }, function () {
1782
- ctx.run({ groupName: groupName }, tests);
1373
+ context.run({ groupName: groupName }, tests);
1783
1374
  });
1784
1375
  }
1785
1376
  function throwGroupError(error) {
@@ -1805,125 +1396,15 @@ function optional(optionals) {
1805
1396
  });
1806
1397
  }
1807
1398
 
1808
- function shouldUseErrorAsMessage(message, error) {
1809
- // kind of cheating with this safe guard, but it does the job
1810
- return isUndefined(message) && isStringValue(error);
1399
+ function skipWhen(conditional, callback) {
1400
+ isolate({ type: IsolateTypes.SKIP_WHEN }, function () {
1401
+ context.run({
1402
+ skipped: optionalFunctionValue(conditional, optionalFunctionValue(produceDraft))
1403
+ }, function () { return callback(); });
1404
+ });
1811
1405
  }
1812
1406
 
1813
- var VestTest = /** @class */ (function () {
1814
- function VestTest(fieldName, testFn, _a) {
1815
- var _b = _a === void 0 ? {} : _a, message = _b.message, groupName = _b.groupName;
1816
- this.id = genId();
1817
- this.warns = false;
1818
- this.status = STATUS_UNTESTED;
1819
- this.fieldName = fieldName;
1820
- this.testFn = testFn;
1821
- if (groupName) {
1822
- this.groupName = groupName;
1823
- }
1824
- if (message) {
1825
- this.message = message;
1826
- }
1827
- }
1828
- VestTest.prototype.run = function () {
1829
- var result;
1830
- try {
1831
- result = this.testFn();
1832
- }
1833
- catch (error) {
1834
- if (shouldUseErrorAsMessage(this.message, error)) {
1835
- this.message = error;
1836
- }
1837
- result = false;
1838
- }
1839
- if (result === false) {
1840
- this.fail();
1841
- }
1842
- return result;
1843
- };
1844
- VestTest.prototype.setStatus = function (status) {
1845
- if (this.isFinalStatus() && status !== STATUS_OMITTED) {
1846
- return;
1847
- }
1848
- this.status = status;
1849
- };
1850
- VestTest.prototype.setPending = function () {
1851
- this.setStatus(STATUS_PENDING);
1852
- };
1853
- VestTest.prototype.fail = function () {
1854
- this.setStatus(this.warns ? STATUS_WARNING : STATUS_FAILED);
1855
- };
1856
- VestTest.prototype.done = function () {
1857
- if (this.isFinalStatus()) {
1858
- return;
1859
- }
1860
- this.setStatus(STATUS_PASSING);
1861
- };
1862
- VestTest.prototype.warn = function () {
1863
- this.warns = true;
1864
- };
1865
- VestTest.prototype.isFinalStatus = function () {
1866
- return this.hasFailures() || this.isCanceled() || this.isPassing();
1867
- };
1868
- VestTest.prototype.skip = function () {
1869
- if (this.isPending()) {
1870
- // Without this condition, the test will be marked as skipped even if it is pending.
1871
- // This means that it will not be counted in "allIncomplete" and its done callbacks
1872
- // will not be called, or will be called prematurely.
1873
- return;
1874
- }
1875
- this.setStatus(STATUS_SKIPPED);
1876
- };
1877
- VestTest.prototype.cancel = function () {
1878
- this.setStatus(STATUS_CANCELED);
1879
- useRefreshTestObjects();
1880
- };
1881
- VestTest.prototype.omit = function () {
1882
- this.setStatus(STATUS_OMITTED);
1883
- };
1884
- VestTest.prototype.valueOf = function () {
1885
- return !this.isFailing();
1886
- };
1887
- VestTest.prototype.hasFailures = function () {
1888
- return this.isFailing() || this.isWarning();
1889
- };
1890
- VestTest.prototype.isPending = function () {
1891
- return this.status === STATUS_PENDING;
1892
- };
1893
- VestTest.prototype.isTested = function () {
1894
- return this.hasFailures() || this.isPassing();
1895
- };
1896
- VestTest.prototype.isOmitted = function () {
1897
- return this.status === STATUS_OMITTED;
1898
- };
1899
- VestTest.prototype.isUntested = function () {
1900
- return this.status === STATUS_UNTESTED;
1901
- };
1902
- VestTest.prototype.isFailing = function () {
1903
- return this.status === STATUS_FAILED;
1904
- };
1905
- VestTest.prototype.isCanceled = function () {
1906
- return this.status === STATUS_CANCELED;
1907
- };
1908
- VestTest.prototype.isSkipped = function () {
1909
- return this.status === STATUS_SKIPPED;
1910
- };
1911
- VestTest.prototype.isPassing = function () {
1912
- return this.status === STATUS_PASSING;
1913
- };
1914
- VestTest.prototype.isWarning = function () {
1915
- return this.status === STATUS_WARNING;
1916
- };
1917
- return VestTest;
1918
- }());
1919
- var STATUS_UNTESTED = 'UNTESTED';
1920
- var STATUS_SKIPPED = 'SKIPPED';
1921
- var STATUS_FAILED = 'FAILED';
1922
- var STATUS_WARNING = 'WARNING';
1923
- var STATUS_PASSING = 'PASSING';
1924
- var STATUS_PENDING = 'PENDING';
1925
- var STATUS_CANCELED = 'CANCELED';
1926
- var STATUS_OMITTED = 'OMITTED';
1407
+ var isNotString = bindNot(isStringValue);
1927
1408
 
1928
1409
  function isPromise(value) {
1929
1410
  return value && isFunction(value.then);
@@ -1951,12 +1432,12 @@ function runAsyncTest(testObject) {
1951
1432
  return;
1952
1433
  var emit = useBus().emit;
1953
1434
  var stateRef = useStateRef();
1954
- var done = ctx.bind({ stateRef: stateRef }, function () {
1435
+ var done = context.bind({ stateRef: stateRef }, function () {
1955
1436
  // invalidating the "produce" cache
1956
1437
  useRefreshTestObjects();
1957
1438
  emit(Events.TEST_COMPLETED, testObject);
1958
1439
  });
1959
- var fail = ctx.bind({ stateRef: stateRef }, function (rejectionMessage) {
1440
+ var fail = context.bind({ stateRef: stateRef }, function (rejectionMessage) {
1960
1441
  if (testObject.isCanceled()) {
1961
1442
  return;
1962
1443
  }
@@ -1978,7 +1459,7 @@ function runAsyncTest(testObject) {
1978
1459
  * Runs sync tests - or extracts promise.
1979
1460
  */
1980
1461
  function runSyncTest(testObject) {
1981
- return ctx.run({ currentTest: testObject }, function () {
1462
+ return context.run({ currentTest: testObject }, function () {
1982
1463
  var result;
1983
1464
  try {
1984
1465
  result = testObject.testFn();
@@ -2000,7 +1481,7 @@ function runSyncTest(testObject) {
2000
1481
  * Registers test, if async - adds to pending array
2001
1482
  */
2002
1483
  function registerTest(testObject) {
2003
- var emit = useBus().emit;
1484
+ var bus = useBus();
2004
1485
  // Run test callback.
2005
1486
  // If a promise is returned, set as async and
2006
1487
  // Move to pending list.
@@ -2014,11 +1495,11 @@ function registerTest(testObject) {
2014
1495
  runAsyncTest(testObject);
2015
1496
  }
2016
1497
  else {
2017
- emit(Events.TEST_COMPLETED, testObject);
1498
+ bus.emit(Events.TEST_COMPLETED, testObject);
2018
1499
  }
2019
1500
  }
2020
1501
  catch (e) {
2021
- throwError("Your test function " + testObject.fieldName + " returned " + JSON.stringify(result) + ". Only \"false\" or a Promise are supported. Return values may cause unexpected behavior.");
1502
+ throwError("Your test function " + testObject.fieldName + " returned a value. Only \"false\" or Promise returns are supported.");
2022
1503
  }
2023
1504
  }
2024
1505
 
@@ -2033,33 +1514,21 @@ function registerTest(testObject) {
2033
1514
  */
2034
1515
  // eslint-disable-next-line max-statements, max-lines-per-function
2035
1516
  function useTestAtCursor(newTestObject) {
2036
- var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
1517
+ var testObjects = useTestObjects()[0];
2037
1518
  var prevTests = testObjects.prev;
2038
1519
  if (isEmpty(prevTests)) {
2039
1520
  useSetTestAtCursor(newTestObject);
2040
1521
  return newTestObject;
2041
1522
  }
2042
1523
  var prevTest = useGetTestAtCursor(prevTests);
1524
+ if (!isNullish(newTestObject.key)) {
1525
+ var nextTest_1 = handleKeyTest(newTestObject.key, newTestObject);
1526
+ useSetTestAtCursor(nextTest_1);
1527
+ return nextTest_1;
1528
+ }
2043
1529
  if (shouldPurgePrevTest(prevTest, newTestObject)) {
2044
1530
  throwTestOrderError(prevTest, newTestObject);
2045
- // Here we handle just the omission of tests in the middle of the test suite.
2046
- // We need to also handle a case in which tests are added in between other tests.
2047
- // At the moment all we can do is just splice the tests out of the array when this happens.
2048
- // A viable solution would be to use something like React's key prop to identify tests regardless
2049
- // of their position in the suite. https://reactjs.org/docs/lists-and-keys.html#keys
2050
- var current = getCurrent(prevTests, usePath());
2051
- var cursorAt = useCursorAt();
2052
- current.splice(cursorAt);
2053
- // We actually don't mind mutating the state directly (as can be seen above). There is no harm in it
2054
- // since we're only touching the "prev" state. The reason we still use the setter function is
2055
- // to prevent future headaches if we ever do need to rely on prev-state immutability.
2056
- setTestObjects(function (_a) {
2057
- var current = _a.current;
2058
- return ({
2059
- prev: prevTests,
2060
- current: current
2061
- });
2062
- });
1531
+ removeAllNextTestsInIsolate();
2063
1532
  // Need to see if this has any effect at all.
2064
1533
  prevTest = null;
2065
1534
  }
@@ -2067,6 +1536,23 @@ function useTestAtCursor(newTestObject) {
2067
1536
  useSetTestAtCursor(nextTest);
2068
1537
  return nextTest;
2069
1538
  }
1539
+ function removeAllNextTestsInIsolate() {
1540
+ var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
1541
+ var prevTests = testObjects.prev;
1542
+ var current = getCurrent(prevTests, usePath());
1543
+ var cursorAt = useCursorAt();
1544
+ current.splice(cursorAt);
1545
+ // We actually don't mind mutating the state directly (as can be seen above). There is no harm in it
1546
+ // since we're only touching the "prev" state. The reason we still use the setter function is
1547
+ // to prevent future headaches if we ever do need to rely on prev-state immutability.
1548
+ setTestObjects(function (_a) {
1549
+ var current = _a.current;
1550
+ return ({
1551
+ prev: prevTests,
1552
+ current: current
1553
+ });
1554
+ });
1555
+ }
2070
1556
  function useSetTestAtCursor(testObject) {
2071
1557
  var cursorPath = usePath();
2072
1558
  useSetTests(function (tests) {
@@ -2084,13 +1570,26 @@ function throwTestOrderError(prevTest, newTestObject) {
2084
1570
  if (shouldAllowReorder()) {
2085
1571
  return;
2086
1572
  }
2087
- throwErrorDeferred("Vest Critical Error: Tests called in different order than previous run.\n expected: " + prevTest.fieldName + "\n received: " + newTestObject.fieldName + "\n This happens when you conditionally call your tests using if/else.\n This might lead to incorrect validation results.\n Replacing if/else with skipWhen solves these issues.");
1573
+ throwErrorDeferred("Vest Critical Error: Tests called in different order than previous run.\n expected: " + prevTest.fieldName + "\n received: " + newTestObject.fieldName + "\n This can happen on one of two reasons:\n 1. You're using if/else statements to conditionally select tests. Instead, use \"skipWhen\".\n 2. You are iterating over a list of tests, and their order changed. Use \"each\" and a custom key prop so that Vest retains their state.");
1574
+ }
1575
+ function handleKeyTest(key, newTestObject) {
1576
+ var prevTestByKey = usePrevTestByKey(key);
1577
+ var nextTest = newTestObject;
1578
+ if (prevTestByKey) {
1579
+ nextTest = prevTestByKey;
1580
+ }
1581
+ useRetainTestKey(key, nextTest);
1582
+ return nextTest;
2088
1583
  }
2089
1584
 
2090
1585
  function registerPrevRunTest(testObject) {
2091
1586
  var prevRunTest = useTestAtCursor(testObject);
2092
1587
  if (isExcluded(testObject)) {
2093
- testObject.skip();
1588
+ // We're forcing skipping the pending test
1589
+ // if we're directly within a skipWhen block
1590
+ // This mostly means that we're probably giving
1591
+ // up on this async test intentionally.
1592
+ prevRunTest.skip(isExcludedIndividually());
2094
1593
  moveForward();
2095
1594
  return prevRunTest;
2096
1595
  }
@@ -2110,33 +1609,6 @@ function registerTestObjectByTier(testObject) {
2110
1609
  }
2111
1610
  }
2112
1611
 
2113
- function bindTestEach(test) {
2114
- /**
2115
- * Run multiple tests using a parameter table
2116
- */
2117
- function each(table) {
2118
- if (!isArray(table)) {
2119
- throwError('test.each: Expected table to be an array.');
2120
- }
2121
- function eachReturn(fieldName) {
2122
- var args = [];
2123
- for (var _i = 1; _i < arguments.length; _i++) {
2124
- args[_i - 1] = arguments[_i];
2125
- }
2126
- var _a = args.reverse(), testFn = _a[0], message = _a[1];
2127
- return isolate({ type: IsolateTypes.EACH }, function () {
2128
- return table.map(function (item) {
2129
- item = asArray(item);
2130
- return test(optionalFunctionValue.apply(void 0, __spreadArray([fieldName], item)), optionalFunctionValue.apply(void 0, __spreadArray([message], item)), function () { return testFn.apply(void 0, item); } // eslint-disable-line max-nested-callbacks
2131
- );
2132
- });
2133
- });
2134
- }
2135
- return eachReturn;
2136
- }
2137
- return each;
2138
- }
2139
-
2140
1612
  // eslint-disable-next-line max-lines-per-function
2141
1613
  function bindTestMemo(test) {
2142
1614
  var cache = createCache(100); // arbitrary cache size
@@ -2146,11 +1618,10 @@ function bindTestMemo(test) {
2146
1618
  for (var _i = 1; _i < arguments.length; _i++) {
2147
1619
  args[_i - 1] = arguments[_i];
2148
1620
  }
2149
- var suiteId = useSuiteId()[0];
2150
1621
  var cursorAt = useCursorAt();
2151
1622
  var _a = args.reverse(), deps = _a[0], testFn = _a[1], msg = _a[2];
2152
1623
  // Implicit dependency for more specificity
2153
- var dependencies = [suiteId, fieldName, cursorAt].concat(deps);
1624
+ var dependencies = [useSuiteId(), fieldName, cursorAt].concat(deps);
2154
1625
  var cached = cache.get(dependencies);
2155
1626
  if (isNull(cached)) {
2156
1627
  // cache miss
@@ -2171,24 +1642,52 @@ function testBase(fieldName) {
2171
1642
  for (var _i = 1; _i < arguments.length; _i++) {
2172
1643
  args[_i - 1] = arguments[_i];
2173
1644
  }
2174
- var _a = args.reverse(), testFn = _a[0], message = _a[1];
2175
- var context = ctx.useX();
1645
+ var _a = (isFunction(args[1]) ? args : __spreadArray([undefined], args)), message = _a[0], testFn = _a[1], key = _a[2];
1646
+ if (isNotString(fieldName)) {
1647
+ throwIncompatibleParamsError('fieldName', 'string');
1648
+ }
1649
+ if (!isFunction(testFn)) {
1650
+ throwIncompatibleParamsError('Test callback', 'function');
1651
+ }
1652
+ var context$1 = context.useX();
2176
1653
  var testObject = new VestTest(fieldName, testFn, {
2177
1654
  message: message,
2178
- groupName: context === null || context === void 0 ? void 0 : context.groupName
1655
+ groupName: context$1.groupName,
1656
+ key: key
2179
1657
  });
2180
1658
  return registerPrevRunTest(testObject);
2181
1659
  }
2182
1660
  var test = assign(testBase, {
2183
- each: bindTestEach(testBase),
2184
1661
  memo: bindTestMemo(testBase)
2185
1662
  });
1663
+ function throwIncompatibleParamsError(name, expected) {
1664
+ throwError("Incompatible params passed to test function. " + name + " must be a " + expected);
1665
+ }
1666
+
1667
+ var ERROR_OUTSIDE_OF_TEST = "warn hook called outside of a test callback. It won't have an effect."
1668
+ ;
1669
+ /**
1670
+ * Sets a running test to warn only mode.
1671
+ */
1672
+ function warn() {
1673
+ var ctx = context.useX('warn ' + ERROR_HOOK_CALLED_OUTSIDE);
1674
+ if (!ctx.currentTest) {
1675
+ throwError(ERROR_OUTSIDE_OF_TEST);
1676
+ }
1677
+ ctx.currentTest.warn();
1678
+ }
2186
1679
 
2187
- var VERSION = "4.0.0-dev-7acb76";
1680
+ var VERSION = "4.0.0-dev-1aae50";
2188
1681
 
1682
+ Object.defineProperty(exports, 'enforce', {
1683
+ enumerable: true,
1684
+ get: function () {
1685
+ return n4s.enforce;
1686
+ }
1687
+ });
2189
1688
  exports.VERSION = VERSION;
2190
1689
  exports.create = create;
2191
- exports.enforce = enforce;
1690
+ exports.each = each;
2192
1691
  exports.group = group;
2193
1692
  exports.only = only;
2194
1693
  exports.optional = optional;