vest 4.0.0-dev-366a8b → 4.0.0-dev-e266d9

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