vest 3.2.8-dev-6d7c74 → 3.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +115 -0
  2. package/any.d.ts +3 -0
  3. package/any.js +1 -0
  4. package/classNames.d.ts +14 -0
  5. package/classNames.js +1 -0
  6. package/docs/.nojekyll +0 -0
  7. package/docs/README.md +115 -0
  8. package/docs/_assets/favicon.ico +0 -0
  9. package/docs/_assets/vest-logo.png +0 -0
  10. package/docs/_sidebar.md +19 -0
  11. package/docs/_sidebar.md.bak +14 -0
  12. package/docs/cross_field_validations.md +79 -0
  13. package/docs/enforce.md +18 -0
  14. package/docs/enforce.md.bak +13 -0
  15. package/docs/exclusion.md +128 -0
  16. package/docs/getting_started.md +79 -0
  17. package/docs/group.md +142 -0
  18. package/docs/index.html +41 -0
  19. package/docs/migration.md +107 -0
  20. package/docs/n4s/compound.md +187 -0
  21. package/docs/n4s/custom.md +52 -0
  22. package/docs/n4s/external.md +54 -0
  23. package/docs/n4s/rules.md +1282 -0
  24. package/docs/n4s/template.md +53 -0
  25. package/docs/node.md +43 -0
  26. package/docs/optional.md +51 -0
  27. package/docs/result.md +238 -0
  28. package/docs/state.md +102 -0
  29. package/docs/test.md +172 -0
  30. package/docs/utilities.md +105 -0
  31. package/docs/warn.md +82 -0
  32. package/enforce.d.ts +230 -0
  33. package/esm/package.json +1 -0
  34. package/esm/vest.es.development.js +2493 -0
  35. package/esm/vest.es.production.js +2490 -0
  36. package/esm/vest.es.production.min.js +1 -0
  37. package/package.json +65 -12
  38. package/promisify.d.ts +7 -0
  39. package/{dist/umd/promisify.production.js → promisify.js} +1 -1
  40. package/schema.d.ts +26 -0
  41. package/schema.js +1 -0
  42. package/vest.cjs.development.js +2494 -0
  43. package/vest.cjs.production.js +2491 -0
  44. package/vest.cjs.production.min.js +1 -0
  45. package/vest.d.ts +254 -0
  46. package/vest.js +7 -0
  47. package/vest.umd.development.js +2711 -0
  48. package/vest.umd.production.js +2708 -0
  49. package/vest.umd.production.min.js +1 -0
  50. package/vestResult.d.ts +105 -0
  51. package/CHANGELOG.md +0 -94
  52. package/LICENSE +0 -21
  53. package/classnames/index.js +0 -7
  54. package/classnames/package.json +0 -1
  55. package/dist/cjs/classnames.development.js +0 -67
  56. package/dist/cjs/classnames.production.js +0 -1
  57. package/dist/cjs/promisify.development.js +0 -20
  58. package/dist/cjs/promisify.production.js +0 -1
  59. package/dist/cjs/vest.development.js +0 -1616
  60. package/dist/cjs/vest.production.js +0 -1
  61. package/dist/es/classnames.development.js +0 -65
  62. package/dist/es/classnames.production.js +0 -1
  63. package/dist/es/promisify.development.js +0 -18
  64. package/dist/es/promisify.production.js +0 -1
  65. package/dist/es/vest.development.js +0 -1604
  66. package/dist/es/vest.production.js +0 -1
  67. package/dist/umd/classnames.development.js +0 -73
  68. package/dist/umd/classnames.production.js +0 -1
  69. package/dist/umd/promisify.development.js +0 -26
  70. package/dist/umd/vest.development.js +0 -1622
  71. package/dist/umd/vest.production.js +0 -1
  72. package/index.js +0 -7
  73. package/promisify/index.js +0 -7
  74. package/promisify/package.json +0 -1
  75. package/types/classnames.d.ts +0 -71
  76. package/types/classnames.d.ts.map +0 -1
  77. package/types/promisify.d.ts +0 -61
  78. package/types/promisify.d.ts.map +0 -1
  79. package/types/vest.d.ts +0 -271
  80. package/types/vest.d.ts.map +0 -1
@@ -0,0 +1,2494 @@
1
+ 'use strict';
2
+
3
+ function createState(onStateChange) {
4
+ var state = {
5
+ references: []
6
+ };
7
+ var registrations = [];
8
+ return {
9
+ registerStateKey: registerStateKey,
10
+ reset: reset
11
+ };
12
+ /**
13
+ * Registers a new key in the state, takes the initial value (may be a function that returns the initial value), returns a function.
14
+ *
15
+ * @example
16
+ *
17
+ * const useColor = state.registerStateKey("blue");
18
+ *
19
+ * let [color, setColor] = useColor(); // -> ["blue", Function]
20
+ *
21
+ * setColor("green");
22
+ *
23
+ * useColor()[0]; -> "green"
24
+ */
25
+
26
+ function registerStateKey(initialState, onUpdate) {
27
+ var key = registrations.length;
28
+ registrations.push([initialState, onUpdate]);
29
+ return initKey(key, initialState);
30
+ }
31
+
32
+ function reset() {
33
+ state.references = [];
34
+ registrations.forEach(function (_ref, index) {
35
+ var initialValue = _ref[0];
36
+ return initKey(index, initialValue);
37
+ });
38
+ }
39
+
40
+ function initKey(key, initialState) {
41
+ current().push();
42
+ set(key, optionalFunctionValue$1(initialState));
43
+ return function useStateKey() {
44
+ return [current()[key], function (nextState) {
45
+ return set(key, optionalFunctionValue$1(nextState, [current()[key]]));
46
+ }];
47
+ };
48
+ }
49
+
50
+ function current() {
51
+ return state.references;
52
+ }
53
+
54
+ function set(key, value) {
55
+ var prevValue = state.references[key];
56
+ state.references[key] = value;
57
+ var _registrations$key = registrations[key],
58
+ onUpdate = _registrations$key[1];
59
+
60
+ if (isFunction$1(onUpdate)) {
61
+ onUpdate(value, prevValue);
62
+ }
63
+
64
+ if (isFunction$1(onStateChange)) {
65
+ onStateChange();
66
+ }
67
+ }
68
+ }
69
+
70
+ function isFunction$1(f) {
71
+ return typeof f === 'function';
72
+ }
73
+
74
+ function optionalFunctionValue$1(value, args) {
75
+ return isFunction$1(value) ? value.apply(null, args) : value;
76
+ }
77
+
78
+ function asArray(possibleArg) {
79
+ return [].concat(possibleArg);
80
+ }
81
+
82
+ function createStateRef(state, {
83
+ suiteId,
84
+ name
85
+ }) {
86
+ return {
87
+ carryOverTests: state.registerStateKey(() => []),
88
+ optionalFields: state.registerStateKey(() => ({})),
89
+ pending: state.registerStateKey(() => ({
90
+ pending: [],
91
+ lagging: []
92
+ })),
93
+ skippedTests: state.registerStateKey(() => []),
94
+ suiteId: state.registerStateKey(() => ({
95
+ id: suiteId,
96
+ name
97
+ })),
98
+ testCallbacks: state.registerStateKey(() => ({
99
+ fieldCallbacks: {},
100
+ doneCallbacks: []
101
+ })),
102
+ testObjects: state.registerStateKey(() => [])
103
+ };
104
+ }
105
+
106
+ function _extends() {
107
+ _extends = Object.assign || function (target) {
108
+ for (var i = 1; i < arguments.length; i++) {
109
+ var source = arguments[i];
110
+
111
+ for (var key in source) {
112
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
113
+ target[key] = source[key];
114
+ }
115
+ }
116
+ }
117
+
118
+ return target;
119
+ };
120
+
121
+ return _extends.apply(this, arguments);
122
+ }
123
+
124
+ function createContext(init) {
125
+ var storage = {
126
+ ancestry: []
127
+ };
128
+ return {
129
+ run: run,
130
+ bind: bind,
131
+ use: use
132
+ };
133
+
134
+ function run(ctxRef, fn) {
135
+ var _init;
136
+
137
+ var parentContext = use();
138
+
139
+ var out = _extends({}, parentContext ? parentContext : {}, (_init = init === null || init === void 0 ? void 0 : init(ctxRef, parentContext)) !== null && _init !== void 0 ? _init : ctxRef);
140
+
141
+ var ctx = set(Object.freeze(out));
142
+ storage.ancestry.unshift(ctx);
143
+ var res = fn(ctx);
144
+ clear();
145
+ return res;
146
+ }
147
+
148
+ function bind(ctxRef, fn) {
149
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
150
+ args[_key - 2] = arguments[_key];
151
+ }
152
+
153
+ return function () {
154
+ for (var _len2 = arguments.length, runTimeArgs = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
155
+ runTimeArgs[_key2] = arguments[_key2];
156
+ }
157
+
158
+ return run(ctxRef, function () {
159
+ return fn.apply(void 0, args.concat(runTimeArgs));
160
+ });
161
+ };
162
+ }
163
+
164
+ function use() {
165
+ return storage.ctx;
166
+ }
167
+
168
+ function set(value) {
169
+ return storage.ctx = value;
170
+ }
171
+
172
+ function clear() {
173
+ var _storage$ancestry$;
174
+
175
+ storage.ancestry.shift();
176
+ set((_storage$ancestry$ = storage.ancestry[0]) !== null && _storage$ancestry$ !== void 0 ? _storage$ancestry$ : null);
177
+ }
178
+ }
179
+
180
+ var assign = Object.assign;
181
+
182
+ const EXCLUSION_ITEM_TYPE_TESTS = 'tests';
183
+ const EXCLUSION_ITEM_TYPE_GROUPS = 'groups';
184
+
185
+ const context = createContext((ctxRef, parentContext) => parentContext ? null : assign({}, {
186
+ exclusion: {
187
+ [EXCLUSION_ITEM_TYPE_TESTS]: {},
188
+ [EXCLUSION_ITEM_TYPE_GROUPS]: {}
189
+ }
190
+ }, ctxRef));
191
+
192
+ /**
193
+ * @returns a unique numeric id.
194
+ */
195
+ const id = (n => () => `${n++}`)(0);
196
+
197
+ function isFunction (v) {
198
+ return typeof v === 'function';
199
+ }
200
+
201
+ function bindNot(fn) {
202
+ return function () {
203
+ return !fn.apply(this, arguments);
204
+ };
205
+ }
206
+
207
+ function isNull(value) {
208
+ return value === null;
209
+ }
210
+ const isNotNull = bindNot(isNull);
211
+
212
+ function lengthEquals(value, arg1) {
213
+ return value.length === Number(arg1);
214
+ }
215
+ const lengthNotEquals = bindNot(lengthEquals);
216
+
217
+ /**
218
+ * Creates a cache function
219
+ * @param {number} [maxSize] Max cache size
220
+ * @return {Function} cache function
221
+ */
222
+
223
+ const createCache = (maxSize = 10) => {
224
+ const cacheStorage = [];
225
+ /**
226
+ * @param {Any[]} deps dependency array.
227
+ * @param {Function} cache action function.
228
+ */
229
+
230
+ const cache = (deps, cacheAction) => {
231
+ const cacheHit = cache.get(deps);
232
+
233
+ if (isNotNull(cacheHit)) {
234
+ return cacheHit[1];
235
+ }
236
+
237
+ const result = cacheAction();
238
+ cacheStorage.unshift([deps.concat(), result]);
239
+
240
+ if (cacheStorage.length > maxSize) {
241
+ cacheStorage.length = maxSize;
242
+ }
243
+
244
+ return result;
245
+ };
246
+ /**
247
+ * Retrieves an item from the cache.
248
+ * @param {deps} deps Dependency array
249
+ */
250
+
251
+
252
+ cache.get = deps => cacheStorage[cacheStorage.findIndex(([cachedDeps]) => lengthEquals(deps, cachedDeps.length) && deps.every((dep, i) => dep === cachedDeps[i]))] || null;
253
+
254
+ return cache;
255
+ };
256
+
257
+ const SEVERITY_GROUP_WARN = 'warnings';
258
+ const SEVERITY_COUNT_WARN = 'warnCount';
259
+ const SEVERITY_GROUP_ERROR = 'errors';
260
+ const SEVERITY_COUNT_ERROR = 'errorCount';
261
+ const TEST_COUNT = 'testCount';
262
+
263
+ const getStateRef = () => context.use().stateRef;
264
+
265
+ function useCarryOverTests() {
266
+ return getStateRef().carryOverTests();
267
+ }
268
+ function usePending() {
269
+ return getStateRef().pending();
270
+ }
271
+ function useSuiteId() {
272
+ return getStateRef().suiteId();
273
+ }
274
+ function useTestCallbacks() {
275
+ return getStateRef().testCallbacks();
276
+ }
277
+ function useTestObjects() {
278
+ return getStateRef().testObjects();
279
+ }
280
+ function useSkippedTests() {
281
+ return getStateRef().skippedTests();
282
+ }
283
+ function useOptionalFields() {
284
+ return getStateRef().optionalFields();
285
+ }
286
+
287
+ /**
288
+ * Reads the testObjects list and gets full validation result from it.
289
+ */
290
+
291
+ const genTestsSummary = () => {
292
+ const [testObjects] = useTestObjects();
293
+ const [suiteIdState] = useSuiteId();
294
+ const [skippedTests] = useSkippedTests();
295
+ const summary = {
296
+ [SEVERITY_COUNT_ERROR]: 0,
297
+ [SEVERITY_COUNT_WARN]: 0,
298
+ [TEST_COUNT]: 0,
299
+ groups: {},
300
+ name: suiteIdState.name,
301
+ tests: {}
302
+ };
303
+ appendSummary(testObjects);
304
+ appendSummary(skippedTests, true);
305
+ return countFailures(summary);
306
+
307
+ function appendSummary(testObject, skipped) {
308
+ testObject.forEach(testObject => {
309
+ const {
310
+ fieldName,
311
+ groupName
312
+ } = testObject;
313
+ summary.tests[fieldName] = genTestObject(summary.tests, testObject, skipped);
314
+
315
+ if (groupName) {
316
+ summary.groups[groupName] = summary.groups[groupName] || {};
317
+ summary.groups[groupName][fieldName] = genTestObject(summary.groups[groupName], testObject, skipped);
318
+ }
319
+ });
320
+ }
321
+ };
322
+ /**
323
+ * Counts the failed tests and adds global counters
324
+ * @param {Object} summary (generated by genTestsSummary)
325
+ */
326
+
327
+
328
+ const countFailures = summary => {
329
+ for (const test in summary.tests) {
330
+ summary[SEVERITY_COUNT_ERROR] += summary.tests[test][SEVERITY_COUNT_ERROR];
331
+ summary[SEVERITY_COUNT_WARN] += summary.tests[test][SEVERITY_COUNT_WARN];
332
+ summary[TEST_COUNT] += summary.tests[test][TEST_COUNT];
333
+ }
334
+
335
+ return summary;
336
+ };
337
+ /**
338
+ *
339
+ * @param {Object} summaryKey The container for the test result data
340
+ * @param {VestTest} testObject
341
+ * @returns {Object} Test result summary
342
+ */
343
+
344
+ const genTestObject = (summaryKey, testObject, skipped) => {
345
+ const {
346
+ fieldName,
347
+ isWarning,
348
+ failed,
349
+ statement
350
+ } = testObject;
351
+ summaryKey[fieldName] = summaryKey[fieldName] || {
352
+ [SEVERITY_COUNT_ERROR]: 0,
353
+ [SEVERITY_COUNT_WARN]: 0,
354
+ [TEST_COUNT]: 0
355
+ };
356
+ const testKey = summaryKey[fieldName];
357
+
358
+ if (skipped) {
359
+ return testKey;
360
+ }
361
+
362
+ summaryKey[fieldName][TEST_COUNT]++; // Adds to severity group
363
+
364
+ const addTo = (count, group) => {
365
+ testKey[count]++;
366
+
367
+ if (statement) {
368
+ testKey[group] = (testKey[group] || []).concat(statement);
369
+ }
370
+ };
371
+
372
+ if (failed) {
373
+ if (isWarning) {
374
+ addTo(SEVERITY_COUNT_WARN, SEVERITY_GROUP_WARN);
375
+ } else {
376
+ addTo(SEVERITY_COUNT_ERROR, SEVERITY_GROUP_ERROR);
377
+ }
378
+ }
379
+
380
+ return testKey;
381
+ };
382
+
383
+ /**
384
+ * Checks that a given test object matches the currently specified severity level
385
+ * @param {string} severity Represents severity level
386
+ * @param {VestTest} testObject VestTest instance
387
+ * @returns {boolean}
388
+ */
389
+
390
+ function isMatchingSeverityProfile(severity, testObject) {
391
+ return severity !== SEVERITY_GROUP_WARN && testObject.isWarning || severity === SEVERITY_GROUP_WARN && !testObject.isWarning;
392
+ }
393
+
394
+ /**
395
+ * @param {'warn'|'error'} severity Filter by severity.
396
+ * @param {Object} options
397
+ * @param {String} [options.group] Group name for error lookup.
398
+ * @param {String} [options.fieldName] Field name for error lookup.
399
+ * @returns all messages for given criteria.
400
+ */
401
+
402
+ const collectFailureMessages = (severity, options) => {
403
+ const [testObjects] = useTestObjects();
404
+ const {
405
+ group,
406
+ fieldName
407
+ } = options || {};
408
+ const res = testObjects.reduce((collector, testObject) => {
409
+ if (group && testObject.groupName !== group) {
410
+ return collector;
411
+ }
412
+
413
+ if (fieldName && testObject.fieldName !== fieldName) {
414
+ return collector;
415
+ }
416
+
417
+ if (!testObject.failed) {
418
+ return collector;
419
+ }
420
+
421
+ if (isMatchingSeverityProfile(severity, testObject)) {
422
+ return collector;
423
+ }
424
+
425
+ collector[testObject.fieldName] = (collector[testObject.fieldName] || []).concat(testObject.statement);
426
+ return collector;
427
+ }, {});
428
+
429
+ if (fieldName) {
430
+ return res[fieldName] || [];
431
+ } else {
432
+ return res;
433
+ }
434
+ };
435
+
436
+ /**
437
+ * @param {'errors'|'warnings'} severityKey lookup severity
438
+ * @param {string} [fieldName]
439
+ * @returns suite or field's errors or warnings.
440
+ */
441
+
442
+ function getFailures(severityKey, fieldName) {
443
+ return collectFailureMessages(severityKey, {
444
+ fieldName
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Throws a timed out error.
450
+ * @param {String} message Error message to display.
451
+ * @param {Error} [type] Alternative Error type.
452
+ */
453
+ const throwError = (message, type = Error) => {
454
+ throw new type(`[${"vest"}]: ${message}`);
455
+ };
456
+
457
+ /**
458
+ * Gets failure messages by group.
459
+ * @param {'errors'|'warnings'} severityKey lookup severity
460
+ * @param {string} group Group name.
461
+ * @param {string} [fieldName] Field name.
462
+ */
463
+
464
+ const getByGroup = (severityKey, group, fieldName) => {
465
+ if (!group) {
466
+ throwError(`get${severityKey[0].toUpperCase()}${severityKey.slice(1)}ByGroup requires a group name. Received \`${group}\` instead.`);
467
+ }
468
+
469
+ return collectFailureMessages(severityKey, {
470
+ group,
471
+ fieldName
472
+ });
473
+ };
474
+
475
+ /**
476
+ * Determines whether a certain test profile has failures.
477
+ * @param {VestTest} testObject
478
+ * @param {'warnings'|'errors'} severityKey lookup severity
479
+ * @param {string} [fieldName]
480
+ * @returns {Boolean}
481
+ */
482
+
483
+ const hasLogic = (testObject, severityKey, fieldName) => {
484
+ if (!testObject.failed) {
485
+ return false;
486
+ }
487
+
488
+ if (fieldName && fieldName !== testObject.fieldName) {
489
+ return false;
490
+ }
491
+
492
+ if (isMatchingSeverityProfile(severityKey, testObject)) {
493
+ return false;
494
+ }
495
+
496
+ return true;
497
+ };
498
+ /**
499
+ * @param {'warnings'|'errors'} severityKey lookup severity
500
+ * @param {string} [fieldName]
501
+ * @returns {Boolean} whether a suite or field have errors or warnings.
502
+ */
503
+
504
+ const has = (severityKey, fieldName) => {
505
+ const [testObjects] = useTestObjects();
506
+ return testObjects.some(testObject => hasLogic(testObject, severityKey, fieldName));
507
+ };
508
+
509
+ /**
510
+ * Checks whether there are failures in a given group.
511
+ * @param {'errors'|'warnings'} severityKey lookup severity
512
+ * @param {string} group Group name.
513
+ * @param {string} [fieldName] Field name.
514
+ * @return {boolean}
515
+ */
516
+
517
+ const hasByGroup = (severityKey, group, fieldName) => {
518
+ const [testObjects] = useTestObjects();
519
+ return testObjects.some(testObject => {
520
+ if (group !== testObject.groupName) {
521
+ return false;
522
+ }
523
+
524
+ return hasLogic(testObject, severityKey, fieldName);
525
+ });
526
+ };
527
+
528
+ /**
529
+ * A safe hasOwnProperty access
530
+ */
531
+ function hasOwnProperty(obj, key) {
532
+ return Object.prototype.hasOwnProperty.call(obj, key);
533
+ }
534
+
535
+ function isNumeric(value) {
536
+ const result = !isNaN(parseFloat(value)) && !isNaN(Number(value)) && isFinite(value);
537
+ return Boolean(result);
538
+ }
539
+ const isNotNumeric = bindNot(isNumeric);
540
+
541
+ function isEmpty(value) {
542
+ if (!value) {
543
+ return true;
544
+ } else if (isNumeric(value)) {
545
+ return value === 0;
546
+ } else if (hasOwnProperty(value, 'length')) {
547
+ return lengthEquals(value, 0);
548
+ } else if (typeof value === 'object') {
549
+ return lengthEquals(Object.keys(value), 0);
550
+ }
551
+
552
+ return true;
553
+ }
554
+ const isNotEmpty = bindNot(isEmpty);
555
+
556
+ /**
557
+ * Checks if a given tests, or the suite as a whole still have remaining tests.
558
+ * @param {string} [fieldName]
559
+ * @returns {Boolean}
560
+ */
561
+
562
+ const hasRemainingTests = fieldName => {
563
+ const [{
564
+ pending,
565
+ lagging
566
+ }] = usePending();
567
+ const allIncomplete = pending.concat(lagging);
568
+
569
+ if (isEmpty(allIncomplete)) {
570
+ return false;
571
+ }
572
+
573
+ if (fieldName) {
574
+ return allIncomplete.some(testObject => testObject.fieldName === fieldName);
575
+ }
576
+
577
+ return isNotEmpty(allIncomplete);
578
+ };
579
+
580
+ const HAS_WARNINGS = 'hasWarnings';
581
+ const HAS_ERRORS = 'hasErrors';
582
+
583
+ function setFnName(fn, value) {
584
+ var _Object$getOwnPropert;
585
+
586
+ // Pre ES2015 non standard implementation, "Function.name" is non configurable field
587
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
588
+ return (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(fn, 'name')) !== null && _Object$getOwnPropert !== void 0 && _Object$getOwnPropert.configurable ? Object.defineProperty(fn, 'name', {
589
+ value
590
+ }) : fn;
591
+ }
592
+
593
+ /**
594
+ * ES5 Transpilation increases the size of spread arguments by a lot.
595
+ * Wraps a function and passes its spread params as an array.
596
+ *
597
+ * @param {Function} cb
598
+ * @param {String} [fnName]
599
+ * @return {Function}
600
+ */
601
+
602
+ function withArgs(cb, fnName) {
603
+ return setFnName(function () {
604
+ const args = Array.from(arguments);
605
+ const right = args.splice(cb.length - 1);
606
+ return cb.apply(null, args.concat([right]));
607
+ }, fnName || cb.name);
608
+ }
609
+
610
+ const cache = createCache(20);
611
+ /**
612
+ * @param {boolean} [isDraft]
613
+ * @returns Vest output object.
614
+ */
615
+
616
+ const produce = isDraft => {
617
+ const {
618
+ stateRef
619
+ } = context.use();
620
+ const [testObjects] = useTestObjects();
621
+ const ctxRef = {
622
+ stateRef
623
+ };
624
+ return cache([testObjects, isDraft], context.bind(ctxRef, () => [[HAS_ERRORS, has, SEVERITY_GROUP_ERROR], [HAS_WARNINGS, has, SEVERITY_GROUP_WARN], ['getErrors', getFailures, SEVERITY_GROUP_ERROR], ['getWarnings', getFailures, SEVERITY_GROUP_WARN], ['hasErrorsByGroup', hasByGroup, SEVERITY_GROUP_ERROR], ['hasWarningsByGroup', hasByGroup, SEVERITY_GROUP_WARN], ['getErrorsByGroup', getByGroup, SEVERITY_GROUP_ERROR], ['getWarningsByGroup', getByGroup, SEVERITY_GROUP_WARN]].concat([['isValid', isValid]], isDraft ? [] : [['done', withArgs(done)]]).reduce((properties, [name, fn, severityKey]) => {
625
+ properties[name] = context.bind(ctxRef, fn, severityKey);
626
+ return properties;
627
+ }, genTestsSummary())));
628
+ };
629
+ /**
630
+ * Registers done callbacks.
631
+ * @param {string} [fieldName]
632
+ * @param {Function} doneCallback
633
+ * @register {Object} Vest output object.
634
+ */
635
+
636
+ function done(args) {
637
+ const [callback, fieldName] = args.reverse();
638
+ const {
639
+ stateRef
640
+ } = context.use();
641
+ const output = produce(); // If we do not have any test runs for the current field
642
+
643
+ const shouldSkipRegistration = fieldName && (!output.tests[fieldName] || output.tests[fieldName].testCount === 0);
644
+
645
+ if (!isFunction(callback) || shouldSkipRegistration) {
646
+ return output;
647
+ }
648
+
649
+ const cb = context.bind({
650
+ stateRef
651
+ }, () => callback(produce(
652
+ /*isDraft:*/
653
+ true))); // is suite finished || field name exists, and test is finished
654
+
655
+ const shouldRunCallback = !hasRemainingTests() || fieldName && !hasRemainingTests(fieldName);
656
+
657
+ if (shouldRunCallback) {
658
+ cb();
659
+ return output;
660
+ }
661
+
662
+ const [, setTestCallbacks] = useTestCallbacks();
663
+ setTestCallbacks(current => {
664
+ if (fieldName) {
665
+ current.fieldCallbacks[fieldName] = (current.fieldCallbacks[fieldName] || []).concat(cb);
666
+ } else {
667
+ current.doneCallbacks.push(cb);
668
+ }
669
+
670
+ return current;
671
+ });
672
+ return output;
673
+ }
674
+
675
+ function isValid() {
676
+ const result = produce();
677
+
678
+ if (result.hasErrors()) {
679
+ return false;
680
+ }
681
+
682
+ const [testObjects] = useTestObjects();
683
+
684
+ if (testObjects.length === 0) {
685
+ return false;
686
+ }
687
+
688
+ const [optionalFields] = useOptionalFields();
689
+ const [{
690
+ pending,
691
+ lagging
692
+ }] = usePending();
693
+
694
+ if (isNotEmpty(pending.concat(lagging).filter(testObject => !testObject.isWarning))) {
695
+ return false;
696
+ }
697
+
698
+ for (const test in result.tests) {
699
+ if (!optionalFields[test] && result.tests[test].testCount === 0) {
700
+ return false;
701
+ }
702
+ }
703
+
704
+ return true;
705
+ }
706
+
707
+ /**
708
+ * Initializes a validation suite, creates a validation context.
709
+ * @param {String} [name] Identifier for validation suite.
710
+ * @param {Function} tests Validation suite body.
711
+ * @returns {Function} validator function.
712
+ */
713
+
714
+ const createSuite = withArgs(args => {
715
+ const [tests, name] = args.reverse();
716
+
717
+ if (!isFunction(tests)) {
718
+ throwError('Suite initialization error. Expected `tests` to be a function.');
719
+ }
720
+
721
+ const handlers = [];
722
+ const state = createState(() => {
723
+ handlers.forEach(fn => fn({
724
+ suiteState: stateRef,
725
+ type: 'suiteStateUpdate'
726
+ }));
727
+ });
728
+ const stateRef = createStateRef(state, {
729
+ suiteId: id(),
730
+ name
731
+ });
732
+ /*
733
+ context.bind returns our `validate` function
734
+ We then wrap it with defineProperties to add
735
+ the `get`, and `reset` functions.
736
+ */
737
+
738
+ const suite = context.bind({
739
+ stateRef
740
+ }, function () {
741
+ const [previousTestObjects] = useTestObjects();
742
+ const [, setCarryOverTests] = useCarryOverTests();
743
+ const [{
744
+ pending
745
+ }, setPending] = usePending();
746
+ state.reset();
747
+ setCarryOverTests(() => previousTestObjects); // Move all the active pending tests to the lagging array
748
+
749
+ setPending({
750
+ lagging: pending,
751
+ pending: []
752
+ }); // Run the consumer's callback
753
+
754
+ tests.apply(null, arguments);
755
+ return produce();
756
+ });
757
+ suite.get = context.bind({
758
+ stateRef
759
+ }, produce,
760
+ /*isDraft:*/
761
+ true);
762
+ suite.reset = state.reset;
763
+ suite.remove = context.bind({
764
+ stateRef
765
+ }, name => {
766
+ const [testObjects] = useTestObjects(); // We're mutating the array in `cancel`, so we have to first copy it.
767
+
768
+ asArray(testObjects).forEach(testObject => {
769
+ if (testObject.fieldName === name) {
770
+ testObject.cancel();
771
+ }
772
+ });
773
+ });
774
+
775
+ suite.subscribe = function (handler) {
776
+ if (!isFunction(handler)) return;
777
+ handlers.push(handler);
778
+ handler({
779
+ type: 'suiteSubscribeInit',
780
+ suiteState: stateRef
781
+ });
782
+ };
783
+
784
+ return suite;
785
+ });
786
+
787
+ /**
788
+ * Stores values and configuration passed down to compound rules.
789
+ *
790
+ * @param {Object} content
791
+ */
792
+
793
+ function EnforceContext(content) {
794
+ assign(this, content);
795
+ }
796
+ /**
797
+ * Sets an EnforceContext config `failFast`
798
+ *
799
+ * @param {Boolean} failFast
800
+ * @return {EnforceContext}
801
+ */
802
+
803
+ EnforceContext.prototype.setFailFast = function (failFast) {
804
+ this.failFast = !!failFast;
805
+ return this;
806
+ };
807
+ /**
808
+ * Extracts the literal value from an EnforceContext object
809
+ * @param {*} value
810
+ * @return {*}
811
+ */
812
+
813
+
814
+ EnforceContext.unwrap = function unwrap(value) {
815
+ return EnforceContext.is(value) ? value.value : value;
816
+ };
817
+ /**
818
+ * Wraps a literal value within a context.
819
+ * @param {*} value
820
+ * @return {EnforceContext}
821
+ */
822
+
823
+
824
+ EnforceContext.wrap = function wrap(value) {
825
+ return EnforceContext.is(value) ? value : new EnforceContext({
826
+ value
827
+ });
828
+ };
829
+ /**
830
+ * Checks whether a given value is an EnforceContext instance
831
+ *
832
+ * @param {*} value
833
+ * @returns {boolean}
834
+ */
835
+
836
+
837
+ EnforceContext.is = function is(value) {
838
+ return value instanceof EnforceContext;
839
+ };
840
+
841
+ function isBoolean(value) {
842
+ return !!value === value;
843
+ }
844
+
845
+ const isNotBoolean = bindNot(isBoolean);
846
+
847
+ function isUndefined(value) {
848
+ return value === undefined;
849
+ }
850
+ const isNotUndefined = bindNot(isUndefined);
851
+
852
+ /**
853
+ * Stores a rule result in an easy to inspect and manipulate structure.
854
+ *
855
+ * @param {boolean|RuleResult} ruleRunResult
856
+ */
857
+
858
+ function RuleResult(ruleRunResult) {
859
+ if (isUndefined(ruleRunResult)) {
860
+ return;
861
+ }
862
+
863
+ if (isBoolean(ruleRunResult)) {
864
+ this.setFailed(!ruleRunResult);
865
+ } else {
866
+ this.extend(ruleRunResult);
867
+ }
868
+ }
869
+ /**
870
+ * Determines whether a given value is a RuleResult instance
871
+ * @param {*} res
872
+ * @return {boolean}
873
+ */
874
+
875
+ RuleResult.is = function (res) {
876
+ return res instanceof RuleResult;
877
+ };
878
+ /**
879
+ * Marks the current result object as an array
880
+ */
881
+
882
+
883
+ RuleResult.prototype.asArray = function () {
884
+ this.isArray = true;
885
+ return this;
886
+ };
887
+ /**
888
+ * @param {string} key
889
+ * @param {value} value
890
+ * @return {RuleResult} current instance
891
+ */
892
+
893
+
894
+ RuleResult.prototype.setAttribute = function (key, value) {
895
+ this[key] = value;
896
+ return this;
897
+ };
898
+ /**
899
+ * @param {boolean} failed
900
+ * @return {RuleResult} current instance
901
+ */
902
+
903
+
904
+ RuleResult.prototype.setFailed = function (failed) {
905
+ this.setAttribute(this.warn ? HAS_WARNINGS : HAS_ERRORS, failed);
906
+ return this.setAttribute('failed', failed);
907
+ };
908
+ /**
909
+ * Adds a nested result object
910
+ *
911
+ * @param {string} key
912
+ * @param {RuleResult} child
913
+ */
914
+
915
+
916
+ RuleResult.prototype.setChild = function (key, child) {
917
+ if (isNull(child)) {
918
+ return null;
919
+ }
920
+
921
+ const isWarning = this[HAS_WARNINGS] || child[HAS_WARNINGS] || child.warn || this.warn;
922
+ this.setAttribute(HAS_WARNINGS, isWarning && child.failed || false);
923
+ this.setAttribute(HAS_ERRORS, this[HAS_ERRORS] || child[HAS_ERRORS] || !isWarning && child.failed || false);
924
+ this.setFailed(this.failed || child.failed);
925
+ this.children = this.children || {};
926
+ this.children[key] = child;
927
+ return child;
928
+ };
929
+ /**
930
+ * Retrieves a child by its key
931
+ *
932
+ * @param {string} key
933
+ * @return {RuleResult|undefined}
934
+ */
935
+
936
+
937
+ RuleResult.prototype.getChild = function (key) {
938
+ return (this.children || {})[key];
939
+ };
940
+ /**
941
+ * Extends current instance with a new provided result
942
+ * @param {Boolean|RuleResult} newRes
943
+ */
944
+
945
+
946
+ RuleResult.prototype.extend = function (newRes) {
947
+ if (isNull(newRes)) {
948
+ return this;
949
+ }
950
+
951
+ const res = RuleResult.is(newRes) ? newRes : new RuleResult().setAttribute('warn', !!this.warn).setFailed(!newRes);
952
+ const failed = this.failed || res.failed;
953
+ const children = mergeChildren(res, this).children;
954
+ assign(this, res);
955
+
956
+ if (!isEmpty(children)) {
957
+ this.children = children;
958
+ }
959
+
960
+ this.setFailed(failed);
961
+ this.setAttribute(HAS_WARNINGS, !!(this[HAS_WARNINGS] || res[HAS_WARNINGS]));
962
+ this.setAttribute(HAS_ERRORS, !!(this[HAS_ERRORS] || res[HAS_ERRORS]));
963
+ };
964
+
965
+ Object.defineProperty(RuleResult.prototype, 'pass', {
966
+ get() {
967
+ return !this.failed;
968
+ }
969
+
970
+ });
971
+ /**
972
+ * Deeply merge the nested children of compound rules
973
+ *
974
+ * @param {?RuleResult} base
975
+ * @param {?RuleResult} add
976
+ * @return {RuleResult}
977
+ */
978
+
979
+ function mergeChildren(base, add) {
980
+ const isRuleResultBase = RuleResult.is(base);
981
+ const isRuleResultAdd = RuleResult.is(add); // If both base and add are result objects
982
+
983
+ if (isRuleResultBase && isRuleResultAdd) {
984
+ // Use failed if either is failing
985
+ base.setFailed(base.failed || add.failed); // If neither has a children object, or the children object is
986
+
987
+ if (isEmpty(base.children) && isEmpty(add.children)) {
988
+ return base;
989
+ } // If both have a children object
990
+
991
+
992
+ if (base.children && add.children) {
993
+ // Merge all the "right side" children back to base
994
+ for (const key in base.children) {
995
+ mergeChildren(base.children[key], add.children[key]);
996
+ } // If a child exists in "add" but not in "base", just copy the child as is
997
+
998
+
999
+ for (const key in add.children) {
1000
+ if (!hasOwnProperty(base.children, key)) {
1001
+ base.setChild(key, add.children[key]);
1002
+ }
1003
+ } // Return the modified base object
1004
+
1005
+
1006
+ return base; // If base has no children (but add does)
1007
+ } else if (!base.children) {
1008
+ // Use add's children
1009
+ base.children = add.children; // If add has no children
1010
+ } else if (!add.children) {
1011
+ // return base as is
1012
+ return base;
1013
+ } // If only base is `RuleResult`
1014
+
1015
+ } else if (isRuleResultBase) {
1016
+ // Return base as is
1017
+ return base; // If only add is RuleResult
1018
+ } else if (isRuleResultAdd) {
1019
+ // Return add as is
1020
+ return add;
1021
+ } // That's a weird case. Let's fail. Very unlikely.
1022
+
1023
+
1024
+ return new RuleResult(false);
1025
+ }
1026
+
1027
+ const RUN_RULE = 'run';
1028
+ const TEST_RULE = 'test';
1029
+ const MODE_ALL = 'all';
1030
+ const MODE_ONE = 'one';
1031
+ const MODE_ANY = 'any';
1032
+
1033
+ /**
1034
+ * Determines whether we should bail out of an enforcement.
1035
+ *
1036
+ * @param {EnforceContext} ctx
1037
+ * @param {RuleResult} result
1038
+ */
1039
+
1040
+ function shouldFailFast(ctx, result) {
1041
+ if (result.pass || result.warn) {
1042
+ return false;
1043
+ }
1044
+
1045
+ return !!EnforceContext.is(ctx) && ctx.failFast;
1046
+ }
1047
+
1048
+ /**
1049
+ * Runs multiple enforce rules that are passed to compounds.
1050
+ * Example: enforce.allOf(enforce.ruleOne(), enforce.ruleTwo().ruleThree())
1051
+ *
1052
+ * @param {{run: Function}[]} ruleGroups
1053
+ * @param {*} value
1054
+ * @return {RuleResult}
1055
+ */
1056
+
1057
+ function runLazyRules(ruleGroups, value) {
1058
+ const result = new RuleResult(true);
1059
+
1060
+ for (const chain of asArray(ruleGroups)) {
1061
+ if (shouldFailFast(value, result)) {
1062
+ break;
1063
+ }
1064
+
1065
+ result.extend(runLazyRule(chain, value));
1066
+ }
1067
+
1068
+ return result;
1069
+ }
1070
+ /**
1071
+ * Runs a single lazy rule
1072
+ *
1073
+ * @param {{run: Function}} ruleGroup
1074
+ * @param {*} value
1075
+ * @return {boolean|RuleResult}
1076
+ */
1077
+
1078
+ function runLazyRule(ruleGroup, value) {
1079
+ return ruleGroup[RUN_RULE](value);
1080
+ }
1081
+
1082
+ /**
1083
+ * Runs chains of rules
1084
+ *
1085
+ * @param {EnforceContext} value
1086
+ * @param {[{test: Function, run: Function}]} rules
1087
+ * @param {RuleResult} options
1088
+ */
1089
+
1090
+ function runCompoundChain(value, rules, options) {
1091
+ const result = new RuleResult(true);
1092
+
1093
+ if (isEmpty(rules)) {
1094
+ result.setFailed(true);
1095
+ }
1096
+
1097
+ const failedResults = [];
1098
+ let count = 0;
1099
+
1100
+ for (const chain of rules) {
1101
+ // Inner result for each iteration
1102
+ const currentResult = runLazyRule(chain, value);
1103
+
1104
+ if (isNull(currentResult)) {
1105
+ return null;
1106
+ }
1107
+
1108
+ const pass = currentResult.pass;
1109
+
1110
+ if (pass) {
1111
+ count++;
1112
+ } else {
1113
+ failedResults.push(currentResult);
1114
+ }
1115
+
1116
+ if (options) {
1117
+ // "anyOf" is a special case.
1118
+ // It shouldn't extend with failed results,
1119
+ // that's why we continue
1120
+ if (options.mode === MODE_ANY) {
1121
+ if (pass) {
1122
+ result.extend(currentResult);
1123
+ break;
1124
+ }
1125
+
1126
+ continue;
1127
+ }
1128
+
1129
+ result.extend(currentResult); // MODE_ALL: All must pass.
1130
+ // If one failed, exit.
1131
+
1132
+ if (options.mode === MODE_ALL) {
1133
+ if (!pass) {
1134
+ break;
1135
+ }
1136
+ } // MODE_ONE: only one must pass.
1137
+ // If more than one passed, exit.
1138
+
1139
+
1140
+ if (options.mode === MODE_ONE) {
1141
+ result.setFailed(count !== 1);
1142
+
1143
+ if (count > 1) {
1144
+ break;
1145
+ }
1146
+ }
1147
+ } else {
1148
+ result.extend(currentResult);
1149
+
1150
+ if (pass) {
1151
+ break;
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ if (result.pass && count === 0) {
1157
+ result.setFailed(true); // In some cases we do not want to extend failures, for example - in ANY
1158
+ // when there is a valid response, so we do it before returning
1159
+
1160
+ failedResults.forEach(failedResult => result.extend(failedResult));
1161
+ }
1162
+
1163
+ return result;
1164
+ }
1165
+
1166
+ /**
1167
+ * Runs a chain of rules, making sure that all assertions pass
1168
+ *
1169
+ * @param {EnforceContext} value
1170
+ * @param {[{test: Function, run: Function}]} ruleChains
1171
+ * @return {RuleResult}
1172
+ */
1173
+
1174
+ function allOf(value, rules) {
1175
+ return runCompoundChain(value, rules, {
1176
+ mode: MODE_ALL
1177
+ });
1178
+ }
1179
+
1180
+ var allOf$1 = withArgs(allOf);
1181
+
1182
+ /**
1183
+ * Runs chains of rules, making sure
1184
+ * that at least one assertion passes
1185
+ *
1186
+ * @param {EnforceContext} value
1187
+ * @param {[{test: Function, run: Function}]} ruleChains
1188
+ * @return {RuleResult}
1189
+ */
1190
+
1191
+ function anyOf(value, ruleChains) {
1192
+ return runCompoundChain(value, ruleChains, {
1193
+ mode: MODE_ANY
1194
+ });
1195
+ }
1196
+
1197
+ var anyOf$1 = withArgs(anyOf);
1198
+
1199
+ function isArray(value) {
1200
+ return Boolean(Array.isArray(value));
1201
+ }
1202
+ const isNotArray = bindNot(isArray);
1203
+
1204
+ /**
1205
+ * Asserts that each element in an array passes
1206
+ * at least one of the provided rule chain
1207
+ *
1208
+ * @param {EnforceContext} value
1209
+ * @param {[{test: Function, run: Function}]} ruleChains
1210
+ * @return {RuleResult}
1211
+ */
1212
+
1213
+ function isArrayOf(value, ruleChains) {
1214
+ const plainValue = EnforceContext.unwrap(value);
1215
+ const result = new RuleResult(true).asArray(); // Fails if current value is not an array
1216
+
1217
+ if (isNotArray(plainValue)) {
1218
+ return result.setFailed(true);
1219
+ }
1220
+
1221
+ for (let i = 0; i < plainValue.length; i++) {
1222
+ // Set result per each item in the array
1223
+ result.setChild(i, runCompoundChain(new EnforceContext({
1224
+ value: plainValue[i],
1225
+ obj: plainValue,
1226
+ key: i
1227
+ }).setFailFast(value.failFast), ruleChains, {
1228
+ mode: MODE_ANY
1229
+ }));
1230
+ }
1231
+
1232
+ return result;
1233
+ }
1234
+
1235
+ var isArrayOf$1 = withArgs(isArrayOf);
1236
+
1237
+ /**
1238
+ * @param {EnforceContext} value
1239
+ * @param {[{test: Function, run: Function}]} ruleChains
1240
+ * @return {RuleResult}
1241
+ */
1242
+
1243
+ function oneOf(value, rules) {
1244
+ return runCompoundChain(value, rules, {
1245
+ mode: MODE_ONE
1246
+ });
1247
+ }
1248
+
1249
+ var oneOf$1 = withArgs(oneOf);
1250
+
1251
+ /**
1252
+ * @param {Array} ObjectEntry Object and key leading to current value
1253
+ * @param {Function[]} rules Rules to validate the value with
1254
+ */
1255
+
1256
+ function optional$1(inputObject, ruleGroups) {
1257
+ const {
1258
+ obj,
1259
+ key
1260
+ } = inputObject; // If current value is not defined, undefined or null
1261
+ // Pass without further assertions
1262
+
1263
+ if (!hasOwnProperty(obj, key) || isUndefined(obj[key] || isNull(obj[key]))) {
1264
+ return true;
1265
+ } // Pass if exists but no assertions
1266
+
1267
+
1268
+ if (isEmpty(ruleGroups)) {
1269
+ return true;
1270
+ } // Run chain with `all` mode
1271
+
1272
+
1273
+ return runCompoundChain(obj[key], ruleGroups, {
1274
+ mode: MODE_ALL
1275
+ });
1276
+ }
1277
+
1278
+ var optional$2 = withArgs(optional$1);
1279
+
1280
+ /**
1281
+ * @param {EnforceContext} inputObject Data object that gets validated
1282
+ * @param {Object} shapeObj Shape definition
1283
+ * @param {Object} options
1284
+ * @param {boolean} options.loose Ignore extra keys not defined in shapeObj
1285
+ */
1286
+
1287
+ function shape(inputObject, shapeObj, options) {
1288
+ // Extract the object from context
1289
+ const obj = EnforceContext.unwrap(inputObject); // Create a new result object
1290
+
1291
+ const result = new RuleResult(true); // Iterate over the shape keys
1292
+
1293
+ for (const key in shapeObj) {
1294
+ const current = shapeObj[key];
1295
+ const value = obj[key];
1296
+
1297
+ if (shouldFailFast(value, result)) {
1298
+ break;
1299
+ } // Set each key in the result object
1300
+
1301
+
1302
+ result.setChild(key, runLazyRule(current, new EnforceContext({
1303
+ value,
1304
+ obj,
1305
+ key
1306
+ }).setFailFast(inputObject.failFast)));
1307
+ } // If mode is not loose
1308
+
1309
+
1310
+ if (!(options || {}).loose) {
1311
+ // Check that each key in the input object exists in the shape
1312
+ for (const key in obj) {
1313
+ if (!hasOwnProperty(shapeObj, key)) {
1314
+ return result.setFailed(true);
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ return result;
1320
+ }
1321
+ const loose = (obj, shapeObj) => shape(obj, shapeObj, {
1322
+ loose: true
1323
+ });
1324
+
1325
+ var compounds = {
1326
+ allOf: allOf$1,
1327
+ anyOf: anyOf$1,
1328
+ isArrayOf: isArrayOf$1,
1329
+ loose,
1330
+ oneOf: oneOf$1,
1331
+ optional: optional$2,
1332
+ shape
1333
+ };
1334
+
1335
+ /**
1336
+ * Takes a value. If it is a function, runs it and returns the result.
1337
+ * Otherwise, returns the value as is.
1338
+ *
1339
+ * @param {Function|*} value Value to return. Run it if a function.
1340
+ * @param {Any[]} [args] Arguments to pass if a function
1341
+ * @return {Any}
1342
+ */
1343
+
1344
+ function optionalFunctionValue(value, args) {
1345
+ return isFunction(value) ? value.apply(null, args) : value;
1346
+ }
1347
+
1348
+ function message(value, msg) {
1349
+ return optionalFunctionValue(msg, [EnforceContext.unwrap(value)]);
1350
+ }
1351
+
1352
+ function warn$1(_, isWarn = true) {
1353
+ return isWarn;
1354
+ }
1355
+
1356
+ function when(value, condition, bail) {
1357
+ const shouldBail = !optionalFunctionValue(condition, [EnforceContext.unwrap(value)].concat(EnforceContext.is(value) ? [value.key, value.obj] : []));
1358
+ return bail(shouldBail);
1359
+ }
1360
+
1361
+ var ruleMeta = {
1362
+ warn: warn$1,
1363
+ message,
1364
+ when
1365
+ };
1366
+
1367
+ function isStringValue (v) {
1368
+ return String(v) === v;
1369
+ }
1370
+
1371
+ function endsWith(value, arg1) {
1372
+ return isStringValue(value) && isStringValue(arg1) && value.endsWith(arg1);
1373
+ }
1374
+ const doesNotEndWith = bindNot(endsWith);
1375
+
1376
+ function equals(value, arg1) {
1377
+ return value === arg1;
1378
+ }
1379
+ const notEquals = bindNot(equals);
1380
+
1381
+ function greaterThan(value, arg1) {
1382
+ return isNumeric(value) && isNumeric(arg1) && Number(value) > Number(arg1);
1383
+ }
1384
+
1385
+ function greaterThanOrEquals(value, arg1) {
1386
+ return isNumeric(value) && isNumeric(arg1) && Number(value) >= Number(arg1);
1387
+ }
1388
+
1389
+ function inside(value, arg1) {
1390
+ if (Array.isArray(arg1) && /^[s|n|b]/.test(typeof value)) {
1391
+ return arg1.indexOf(value) !== -1;
1392
+ } // both value and arg1 are strings
1393
+
1394
+
1395
+ if (isStringValue(arg1) && isStringValue(value)) {
1396
+ return arg1.indexOf(value) !== -1;
1397
+ }
1398
+
1399
+ return false;
1400
+ }
1401
+ const notInside = bindNot(inside);
1402
+
1403
+ function lessThanOrEquals(value, arg1) {
1404
+ return isNumeric(value) && isNumeric(arg1) && Number(value) <= Number(arg1);
1405
+ }
1406
+
1407
+ function isBetween(value, min, max) {
1408
+ return greaterThanOrEquals(value, min) && lessThanOrEquals(value, max);
1409
+ }
1410
+ const isNotBetween = bindNot(isBetween);
1411
+
1412
+ function isBlank(value) {
1413
+ return typeof value === 'string' && value.trim() === '';
1414
+ }
1415
+ const isNotBlank = bindNot(isBlank);
1416
+
1417
+ /**
1418
+ * Validates that a given value is an even number
1419
+ * @param {Number|String} value Value to be validated
1420
+ * @return {Boolean}
1421
+ */
1422
+
1423
+ const isEven = value => {
1424
+ if (isNumeric(value)) {
1425
+ return value % 2 === 0;
1426
+ }
1427
+
1428
+ return false;
1429
+ };
1430
+
1431
+ function isNaN$1(value) {
1432
+ return Number.isNaN(value);
1433
+ }
1434
+ const isNotNaN = bindNot(isNaN$1);
1435
+
1436
+ function isNegative(value) {
1437
+ if (isNumeric(value)) {
1438
+ return Number(value) < 0;
1439
+ }
1440
+
1441
+ return false;
1442
+ }
1443
+ const isPositive = bindNot(isNegative);
1444
+
1445
+ function isNumber(value) {
1446
+ return Boolean(typeof value === 'number');
1447
+ }
1448
+ const isNotNumber = bindNot(isNumber);
1449
+
1450
+ /**
1451
+ * Validates that a given value is an odd number
1452
+ * @param {Number|String} value Value to be validated
1453
+ * @return {Boolean}
1454
+ */
1455
+
1456
+ const isOdd = value => {
1457
+ if (isNumeric(value)) {
1458
+ return value % 2 !== 0;
1459
+ }
1460
+
1461
+ return false;
1462
+ };
1463
+
1464
+ const isNotString = bindNot(isStringValue);
1465
+
1466
+ function isTruthy(value) {
1467
+ return !!value;
1468
+ }
1469
+ const isFalsy = bindNot(isTruthy);
1470
+
1471
+ function lessThan(value, arg1) {
1472
+ return isNumeric(value) && isNumeric(arg1) && Number(value) < Number(arg1);
1473
+ }
1474
+
1475
+ function longerThan(value, arg1) {
1476
+ return value.length > Number(arg1);
1477
+ }
1478
+
1479
+ function longerThanOrEquals(value, arg1) {
1480
+ return value.length >= Number(arg1);
1481
+ }
1482
+
1483
+ function matches(value, regex) {
1484
+ if (regex instanceof RegExp) {
1485
+ return regex.test(value);
1486
+ } else if (isStringValue(regex)) {
1487
+ return new RegExp(regex).test(value);
1488
+ } else {
1489
+ return false;
1490
+ }
1491
+ }
1492
+ const notMatches = bindNot(matches);
1493
+
1494
+ function numberEquals(value, arg1) {
1495
+ return isNumeric(value) && isNumeric(arg1) && Number(value) === Number(arg1);
1496
+ }
1497
+ const numberNotEquals = bindNot(numberEquals);
1498
+
1499
+ function shorterThan(value, arg1) {
1500
+ return value.length < Number(arg1);
1501
+ }
1502
+
1503
+ function shorterThanOrEquals(value, arg1) {
1504
+ return value.length <= Number(arg1);
1505
+ }
1506
+
1507
+ function startsWith(value, arg1) {
1508
+ return isStringValue(value) && isStringValue(arg1) && value.startsWith(arg1);
1509
+ }
1510
+ const doesNotStartWith = bindNot(startsWith);
1511
+
1512
+ function rules() {
1513
+ return {
1514
+ doesNotEndWith,
1515
+ doesNotStartWith,
1516
+ endsWith,
1517
+ equals,
1518
+ greaterThan,
1519
+ greaterThanOrEquals,
1520
+ gt: greaterThan,
1521
+ gte: greaterThanOrEquals,
1522
+ inside,
1523
+ isArray,
1524
+ isBetween,
1525
+ isBoolean,
1526
+ isBlank,
1527
+ isEmpty,
1528
+ isEven,
1529
+ isFalsy,
1530
+ isNaN: isNaN$1,
1531
+ isNegative,
1532
+ isNotArray,
1533
+ isNotBetween,
1534
+ isNotBlank,
1535
+ isNotBoolean,
1536
+ isNotEmpty,
1537
+ isNotNaN,
1538
+ isNotNull,
1539
+ isNotNumber,
1540
+ isNotNumeric,
1541
+ isNotString,
1542
+ isNotUndefined,
1543
+ isNull,
1544
+ isNumber,
1545
+ isNumeric,
1546
+ isOdd,
1547
+ isPositive,
1548
+ isString: isStringValue,
1549
+ isTruthy,
1550
+ isUndefined,
1551
+ lengthEquals,
1552
+ lengthNotEquals,
1553
+ lessThan,
1554
+ lessThanOrEquals,
1555
+ longerThan,
1556
+ longerThanOrEquals,
1557
+ lt: lessThan,
1558
+ lte: lessThanOrEquals,
1559
+ matches,
1560
+ notEquals,
1561
+ notInside,
1562
+ notMatches,
1563
+ numberEquals,
1564
+ numberNotEquals,
1565
+ shorterThan,
1566
+ shorterThanOrEquals,
1567
+ startsWith
1568
+ };
1569
+ }
1570
+
1571
+ var runtimeRules = assign(rules(), compounds, ruleMeta);
1572
+
1573
+ /**
1574
+ * Determines whether a given string is a name of a rule
1575
+ *
1576
+ * @param {string} name
1577
+ * @return {boolean}
1578
+ */
1579
+
1580
+ const isRule = name => hasOwnProperty(runtimeRules, name) && isFunction(runtimeRules[name]);
1581
+
1582
+ const GLOBAL_OBJECT = Function('return this')();
1583
+
1584
+ const proxySupported = () => isFunction(GLOBAL_OBJECT.Proxy);
1585
+
1586
+ function genRuleProxy(target, output) {
1587
+ if (proxySupported()) {
1588
+ return new Proxy(target, {
1589
+ get: (target, fnName) => {
1590
+ // A faster bailout when we access `run` and `test`
1591
+ if (hasOwnProperty(target, fnName)) {
1592
+ return target[fnName];
1593
+ }
1594
+
1595
+ if (isRule(fnName)) {
1596
+ return output(fnName);
1597
+ }
1598
+
1599
+ return target[fnName];
1600
+ }
1601
+ });
1602
+ } else {
1603
+ /**
1604
+ * This method is REALLY not recommended as it is slow and iterates over
1605
+ * all the rules for each direct enforce reference. We only use it as a
1606
+ * lightweight alternative for the much faster proxy interface
1607
+ */
1608
+ for (const fnName in runtimeRules) {
1609
+ if (!isFunction(target[fnName])) {
1610
+ Object.defineProperties(target, {
1611
+ [fnName]: {
1612
+ get: () => output(fnName)
1613
+ }
1614
+ });
1615
+ }
1616
+ }
1617
+
1618
+ return target;
1619
+ }
1620
+ }
1621
+
1622
+ /**
1623
+ * Determines whether a given rule is a compound.
1624
+ *
1625
+ * @param {Function} rule
1626
+ * @return {boolean}
1627
+ */
1628
+
1629
+ function isCompound(rule) {
1630
+ return hasOwnProperty(compounds, rule.name);
1631
+ }
1632
+
1633
+ /**
1634
+ * Creates a rule of lazily called rules.
1635
+ * Each rule gets added a `.run()` property
1636
+ * which runs all the accumulated rules in
1637
+ * the chain against the supplied value
1638
+ *
1639
+ * @param {string} ruleName
1640
+ * @return {{run: Function}}
1641
+ */
1642
+
1643
+ function bindLazyRule(ruleName) {
1644
+ const registeredRules = []; // Chained rules
1645
+
1646
+ const meta = []; // Meta properties to add onto the rule context
1647
+ // Appends a function to the registeredRules array.
1648
+ // It gets called every time the consumer usess chaining
1649
+ // so, for example - enforce.isArray() <- this calles addFn
1650
+
1651
+ const addFn = ruleName => {
1652
+ return withArgs(args => {
1653
+ const rule = runtimeRules[ruleName]; // Add a meta function
1654
+
1655
+ if (ruleMeta[rule.name] === rule) {
1656
+ meta.push((value, ruleResult, bail) => {
1657
+ ruleResult.setAttribute(rule.name, rule(value, args[0], bail));
1658
+ });
1659
+ } else {
1660
+ // Register a rule
1661
+ registeredRules.push(setFnName(value => {
1662
+ return rule.apply(null, [// If the rule is compound - wraps the value with context
1663
+ // Otherwise - unwraps it
1664
+ isCompound(rule) ? EnforceContext.wrap(value) : EnforceContext.unwrap(value)].concat(args));
1665
+ }, ruleName));
1666
+ } // set addFn as the proxy handler
1667
+
1668
+
1669
+ const returnvalue = genRuleProxy({}, addFn);
1670
+
1671
+ returnvalue[RUN_RULE] = value => {
1672
+ const result = new RuleResult(true);
1673
+ let bailed = false; // Run meta chains
1674
+
1675
+ meta.forEach(fn => {
1676
+ fn(value, result, shouldBail => bailed = shouldBail);
1677
+ });
1678
+
1679
+ if (bailed) {
1680
+ return null;
1681
+ } // Iterate over all the registered rules
1682
+ // This runs the function that's inside `addFn`
1683
+
1684
+
1685
+ for (const fn of registeredRules) {
1686
+ try {
1687
+ result.extend(fn(value)); // If a chained rule fails, exit. We failed.
1688
+
1689
+ if (!result.pass) {
1690
+ break;
1691
+ }
1692
+ } catch (e) {
1693
+ result.setFailed(true);
1694
+ break;
1695
+ }
1696
+ }
1697
+
1698
+ return result;
1699
+ };
1700
+
1701
+ returnvalue[TEST_RULE] = value => returnvalue[RUN_RULE](EnforceContext.wrap(value).setFailFast(true)).pass;
1702
+
1703
+ return returnvalue;
1704
+ }, ruleName);
1705
+ }; // Returns the initial rule in the chain
1706
+
1707
+
1708
+ return addFn(ruleName);
1709
+ }
1710
+
1711
+ function bindExtend(enforce, Enforce) {
1712
+ enforce.extend = customRules => {
1713
+ assign(runtimeRules, customRules);
1714
+
1715
+ if (!proxySupported()) {
1716
+ genRuleProxy(Enforce, bindLazyRule);
1717
+ }
1718
+
1719
+ return enforce;
1720
+ };
1721
+ }
1722
+
1723
+ function validateResult(result, rule) {
1724
+ // if result is boolean, or if result.pass is boolean
1725
+ if (isBoolean(result) || result && isBoolean(result.pass)) {
1726
+ return;
1727
+ }
1728
+
1729
+ throwError(`${rule.name} wrong return value for the rule please check that the return is valid` );
1730
+ } // for easier testing and mocking
1731
+
1732
+ function getDefaultResult(value) {
1733
+ return {
1734
+ message: new Error(`invalid ${typeof value} value`)
1735
+ };
1736
+ }
1737
+ /**
1738
+ * Transform the result of a rule into a standard format
1739
+ * @param {string} interfaceName to be used in the messages
1740
+ * @param {*} result of the rule
1741
+ * @param {Object} options
1742
+ * @param {function} options.rule
1743
+ * @param {*} options.value
1744
+ * @returns {Object} result
1745
+ * @returns {string} result.message
1746
+ * @returns {boolean} result.pass indicates if the test passes or not
1747
+ */
1748
+
1749
+ function transformResult(result, {
1750
+ rule,
1751
+ value
1752
+ }) {
1753
+ const defaultResult = getDefaultResult(value);
1754
+ validateResult(result, rule); // if result is boolean
1755
+
1756
+ if (isBoolean(result)) {
1757
+ return defaultResult.pass = result, defaultResult;
1758
+ } else {
1759
+ defaultResult.pass = result.pass;
1760
+
1761
+ if (result.message) {
1762
+ defaultResult.message = optionalFunctionValue(result.message);
1763
+ }
1764
+
1765
+ return defaultResult;
1766
+ }
1767
+ }
1768
+
1769
+ /**
1770
+ * Run a single rule against enforced value (e.g. `isNumber()`)
1771
+ *
1772
+ * @param {Function} rule - rule to run
1773
+ * @param {Any} value
1774
+ * @param {Array} args list of arguments sent from consumer
1775
+ * @throws
1776
+ */
1777
+
1778
+ function runner(rule, value, args = []) {
1779
+ let result;
1780
+ const isCompoundRule = isCompound(rule);
1781
+ const ruleValue = isCompoundRule ? EnforceContext.wrap(value).setFailFast(true) : EnforceContext.unwrap(value);
1782
+ result = rule.apply(null, [ruleValue].concat(args));
1783
+
1784
+ if (!isCompoundRule) {
1785
+ result = transformResult(result, {
1786
+ rule,
1787
+ value
1788
+ });
1789
+ }
1790
+
1791
+ if (!result.pass) {
1792
+ throw result.message;
1793
+ }
1794
+ }
1795
+
1796
+ /**
1797
+ * Adds `template` property to enforce.
1798
+ *
1799
+ * @param {Function} enforce
1800
+ */
1801
+
1802
+ function bindTemplate(enforce) {
1803
+ enforce.template = withArgs(rules => {
1804
+ const template = value => {
1805
+ runner(runLazyRules.bind(null, rules), value);
1806
+ const proxy = genRuleProxy({}, ruleName => withArgs(args => {
1807
+ runner(runtimeRules[ruleName], value, args);
1808
+ return proxy;
1809
+ }));
1810
+ return proxy;
1811
+ }; // `run` returns a deep ResultObject
1812
+
1813
+
1814
+ template[RUN_RULE] = value => runLazyRules(rules, value); // `test` returns a boolean
1815
+
1816
+
1817
+ template[TEST_RULE] = value => runLazyRules(rules, EnforceContext.wrap(value).setFailFast(true)).pass;
1818
+
1819
+ return template;
1820
+ });
1821
+ }
1822
+
1823
+ const Enforce = value => {
1824
+ const proxy = genRuleProxy({}, ruleName => withArgs(args => {
1825
+ runner(runtimeRules[ruleName], value, args);
1826
+ return proxy;
1827
+ }));
1828
+ return proxy;
1829
+ };
1830
+
1831
+ const enforce = genRuleProxy(Enforce, bindLazyRule);
1832
+ bindExtend(enforce, Enforce);
1833
+ bindTemplate(enforce);
1834
+
1835
+ /**
1836
+ * @type {String} Error message to display when a hook was called outside of context.
1837
+ */
1838
+ const ERROR_HOOK_CALLED_OUTSIDE = 'hook called outside of a running suite.';
1839
+
1840
+ /**
1841
+ * Adds a field or multiple fields to inclusion group.
1842
+ * @param {String[]|String} item Item to be added to inclusion group.
1843
+ */
1844
+
1845
+ function only(item) {
1846
+ return addTo(EXCLUSION_GROUP_NAME_ONLY, EXCLUSION_ITEM_TYPE_TESTS, item);
1847
+ }
1848
+
1849
+ only.group = item => addTo(EXCLUSION_GROUP_NAME_ONLY, EXCLUSION_ITEM_TYPE_GROUPS, item);
1850
+ /**
1851
+ * Adds a field or multiple fields to exclusion group.
1852
+ * @param {String[]|String} item Item to be added to exclusion group.
1853
+ */
1854
+
1855
+
1856
+ function skip(item) {
1857
+ return addTo(EXCLUSION_GROUP_NAME_SKIP, EXCLUSION_ITEM_TYPE_TESTS, item);
1858
+ }
1859
+
1860
+ skip.group = item => addTo(EXCLUSION_GROUP_NAME_SKIP, EXCLUSION_ITEM_TYPE_GROUPS, item);
1861
+ /**
1862
+ * Checks whether a certain test profile excluded by any of the exclusion groups.
1863
+ * @param {String} fieldName Field name to test.
1864
+ * @param {VestTest} Test Object reference.
1865
+ * @returns {Boolean}
1866
+ */
1867
+
1868
+
1869
+ function isExcluded(testObject) {
1870
+ const {
1871
+ fieldName,
1872
+ groupName
1873
+ } = testObject;
1874
+ const {
1875
+ exclusion
1876
+ } = context.use();
1877
+ const keyTests = exclusion[EXCLUSION_ITEM_TYPE_TESTS];
1878
+ const testValue = keyTests[fieldName]; // if test is skipped
1879
+ // no need to proceed
1880
+
1881
+ if (testValue === false) {
1882
+ return true;
1883
+ }
1884
+
1885
+ const isTestIncluded = testValue === true; // If inside a group
1886
+
1887
+ if (groupName) {
1888
+ if (isGroupExcluded(groupName)) {
1889
+ return true; // field excluded by group
1890
+ // if group is `only`ed
1891
+ } else if (exclusion[EXCLUSION_ITEM_TYPE_GROUPS][groupName] === true) {
1892
+ if (isTestIncluded) {
1893
+ return false;
1894
+ } // If there is _ANY_ `only`ed test (and we already know this one isn't)
1895
+
1896
+
1897
+ if (hasIncludedTests(keyTests)) {
1898
+ return true; // Excluded implicitly
1899
+ }
1900
+
1901
+ return keyTests[fieldName] === false;
1902
+ }
1903
+ } // if field is only'ed
1904
+
1905
+
1906
+ if (isTestIncluded) {
1907
+ return false;
1908
+ } // If there is _ANY_ `only`ed test (and we already know this one isn't) return true
1909
+ // Otherwise return false
1910
+
1911
+
1912
+ return hasIncludedTests(keyTests);
1913
+ }
1914
+ /**
1915
+ * Checks whether a given group is excluded from running.
1916
+ * @param {String} groupName
1917
+ * @return {Boolean}
1918
+ */
1919
+
1920
+ function isGroupExcluded(groupName) {
1921
+ const {
1922
+ exclusion
1923
+ } = context.use();
1924
+ const keyGroups = exclusion[EXCLUSION_ITEM_TYPE_GROUPS];
1925
+ const groupPresent = hasOwnProperty(keyGroups, groupName); // When group is either only'ed or skipped
1926
+
1927
+ if (groupPresent) {
1928
+ // Return true if group is skipped and false if only'ed
1929
+ return keyGroups[groupName] === false;
1930
+ } // Group is not present
1931
+
1932
+
1933
+ for (const group in keyGroups) {
1934
+ // If any other group is only'ed
1935
+ if (keyGroups[group] === true) {
1936
+ return true;
1937
+ }
1938
+ }
1939
+
1940
+ return false;
1941
+ }
1942
+ /**
1943
+ * @type {String} Exclusion group name: only.
1944
+ */
1945
+
1946
+ const EXCLUSION_GROUP_NAME_ONLY = 'only';
1947
+ /**
1948
+ * @type {String} Exclusion group name: skip.
1949
+ */
1950
+
1951
+ const EXCLUSION_GROUP_NAME_SKIP = 'skip';
1952
+ /**
1953
+ * Adds fields to a specified exclusion group.
1954
+ * @param {String} exclusionGroup To add the fields to.
1955
+ * @param {String} itemType Whether the item is a group or a test.
1956
+ * @param {String[]|String} item A field name or a list of field names.
1957
+ */
1958
+
1959
+ const addTo = (exclusionGroup, itemType, item) => {
1960
+ const ctx = context.use();
1961
+
1962
+ if (!item) {
1963
+ return;
1964
+ }
1965
+
1966
+ if (!ctx) {
1967
+ throwError(`${exclusionGroup} ${ERROR_HOOK_CALLED_OUTSIDE}`);
1968
+ return;
1969
+ }
1970
+
1971
+ asArray(item).forEach(itemName => {
1972
+ if (!isStringValue(itemName)) {
1973
+ return null;
1974
+ }
1975
+
1976
+ ctx.exclusion[itemType][itemName] = exclusionGroup === EXCLUSION_GROUP_NAME_ONLY;
1977
+ });
1978
+ };
1979
+ /**
1980
+ * Checks if context has included tests
1981
+ * @param {Object} keyTests Object containing included and excluded tests
1982
+ * @returns {boolean}
1983
+ */
1984
+
1985
+
1986
+ const hasIncludedTests = keyTests => {
1987
+ for (const test in keyTests) {
1988
+ if (keyTests[test] === true) {
1989
+ return true; // excluded implicitly
1990
+ }
1991
+ }
1992
+
1993
+ return false;
1994
+ };
1995
+
1996
+ // an if statement. The reason for it is to support version 4 api in version 3
1997
+ // so that someone reading the latest docs can still run the code.
1998
+
1999
+ function skipWhen(conditional, callback) {
2000
+ if (isFalsy(optionalFunctionValue(conditional))) {
2001
+ if (isFunction(callback)) {
2002
+ callback();
2003
+ }
2004
+ }
2005
+ }
2006
+
2007
+ /**
2008
+ * @type {String} Error message to display when `warn` gets called outside of a test.
2009
+ */
2010
+
2011
+ const ERROR_OUTSIDE_OF_TEST = "warn hook called outside of a test callback. It won't have an effect." ;
2012
+ /**
2013
+ * Sets a running test to warn only mode.
2014
+ */
2015
+
2016
+ const warn = () => {
2017
+ const ctx = context.use();
2018
+
2019
+ if (!ctx) {
2020
+ throwError('warn ' + ERROR_HOOK_CALLED_OUTSIDE);
2021
+ return;
2022
+ }
2023
+
2024
+ if (!ctx.currentTest) {
2025
+ throwError(ERROR_OUTSIDE_OF_TEST);
2026
+ return;
2027
+ }
2028
+
2029
+ ctx.currentTest.warn();
2030
+ };
2031
+
2032
+ const throwGroupError = () => throwError("group initialization error. Incompatible argument passed to group.");
2033
+ /**
2034
+ * Runs a group callback.
2035
+ * @param {string} groupName
2036
+ * @param {Function} tests
2037
+ */
2038
+
2039
+
2040
+ const group = (groupName, tests) => {
2041
+ if (!isStringValue(groupName)) {
2042
+ throwGroupError();
2043
+ }
2044
+
2045
+ if (!isFunction(tests)) {
2046
+ throwGroupError();
2047
+ } // Running with the context applied
2048
+
2049
+
2050
+ context.bind({
2051
+ groupName
2052
+ }, tests)();
2053
+ };
2054
+
2055
+ function optional(optionals) {
2056
+ const [, setOptionalFields] = useOptionalFields();
2057
+ setOptionalFields(state => {
2058
+ asArray(optionals).forEach(optionalField => {
2059
+ state[optionalField] = true;
2060
+ });
2061
+ return state;
2062
+ });
2063
+ }
2064
+
2065
+ function isSameProfileTest(testObject1, testObject2) {
2066
+ return testObject1.fieldName === testObject2.fieldName && testObject1.groupName === testObject2.groupName;
2067
+ }
2068
+
2069
+ /**
2070
+ * Removes first found element from array
2071
+ * WARNING: Mutates array
2072
+ *
2073
+ * @param {any[]} array
2074
+ * @param {any} element
2075
+ */
2076
+ const removeElementFromArray = (array, element) => {
2077
+ const index = array.indexOf(element);
2078
+
2079
+ if (index !== -1) {
2080
+ array.splice(index, 1);
2081
+ }
2082
+
2083
+ return array;
2084
+ };
2085
+
2086
+ /**
2087
+ * Sets a test as pending in the state.
2088
+ * @param {VestTest} testObject
2089
+ */
2090
+
2091
+ const setPending = testObject => {
2092
+ const [pendingState, setPending] = usePending();
2093
+ const lagging = asArray(pendingState.lagging).reduce((lagging, laggingTestObject) => {
2094
+ /**
2095
+ * If the test is of the same profile
2096
+ * (same name + same group) we cancel
2097
+ * it. Otherwise, it is lagging.
2098
+ */
2099
+ if (isSameProfileTest(testObject, laggingTestObject) && // This last case handles memoized tests
2100
+ // because that retain their od across runs
2101
+ laggingTestObject.id !== testObject.id) {
2102
+ laggingTestObject.cancel();
2103
+ } else {
2104
+ lagging.push(laggingTestObject);
2105
+ }
2106
+
2107
+ return lagging;
2108
+ }, []);
2109
+ setPending(state => ({
2110
+ lagging,
2111
+ pending: state.pending.concat(testObject)
2112
+ }));
2113
+ };
2114
+ /**
2115
+ * Removes a tests from the pending and lagging arrays.
2116
+ * @param {VestTest} testObject
2117
+ */
2118
+
2119
+ const removePending = testObject => {
2120
+ const [, setPending] = usePending();
2121
+ setPending(state => ({
2122
+ pending: removeElementFromArray(state.pending, testObject),
2123
+ lagging: removeElementFromArray(state.lagging, testObject)
2124
+ }));
2125
+ };
2126
+
2127
+ /**
2128
+ * Removes test object from suite state
2129
+ * @param {VestTest} testObject
2130
+ */
2131
+
2132
+ var removeTestFromState = (testObject => {
2133
+ const [, setTestObjects] = useTestObjects();
2134
+ setTestObjects(testObjects => // using asArray to clear the cache.
2135
+ asArray(removeElementFromArray(testObjects, testObject)));
2136
+ });
2137
+
2138
+ /**
2139
+ * Describes a test call inside a Vest suite.
2140
+ * @param {String} fieldName Name of the field being tested.
2141
+ * @param {String} statement The message returned when failing.
2142
+ * @param {Promise|Function} testFn The actual test callback or promise.
2143
+ * @param {string} [group] The group in which the test runs.
2144
+ */
2145
+
2146
+ function VestTest({
2147
+ fieldName,
2148
+ statement,
2149
+ testFn,
2150
+ group
2151
+ }) {
2152
+ const testObject = {
2153
+ cancel,
2154
+ fail,
2155
+ failed: false,
2156
+ fieldName,
2157
+ id: id(),
2158
+ isWarning: false,
2159
+ statement,
2160
+ testFn,
2161
+ valueOf,
2162
+ warn
2163
+ };
2164
+
2165
+ if (group) {
2166
+ testObject.groupName = group;
2167
+ }
2168
+
2169
+ return testObject;
2170
+ /**
2171
+ * @returns {Boolean} Current validity status of a test.
2172
+ */
2173
+
2174
+ function valueOf() {
2175
+ return testObject.failed !== true;
2176
+ }
2177
+ /**
2178
+ * Sets a test to failed.
2179
+ */
2180
+
2181
+
2182
+ function fail() {
2183
+ testObject.failed = true;
2184
+ }
2185
+ /**
2186
+ * Sets a current test's `isWarning` to true.
2187
+ */
2188
+
2189
+
2190
+ function warn() {
2191
+ testObject.isWarning = true;
2192
+ }
2193
+ /**
2194
+ * Marks a test as canceled, removes it from the state.
2195
+ * This function needs to be called within a stateRef context.
2196
+ */
2197
+
2198
+
2199
+ function cancel() {
2200
+ testObject.canceled = true;
2201
+ removePending(this);
2202
+ removeTestFromState(this);
2203
+ }
2204
+ }
2205
+
2206
+ /**
2207
+ *
2208
+ * @param {any[]} array
2209
+ * @param {() => boolean} predicate
2210
+ * @returns {[any[], any[]]}
2211
+ */
2212
+ function partition(array, predicate) {
2213
+ return array.reduce((partitions, value, index) => {
2214
+ partitions[predicate(value, index, array) ? 0 : 1].push(value);
2215
+ return partitions;
2216
+ }, [[], []]);
2217
+ }
2218
+
2219
+ function mergeCarryOverTests(testObject) {
2220
+ const [carryOverTests, setCarryOverTests] = useCarryOverTests();
2221
+ const [, setTestObjects] = useTestObjects();
2222
+ const [moveToTestObjects, keepInCarryOvers] = partition(carryOverTests, carryOverTest => isSameProfileTest(carryOverTest, testObject));
2223
+ setCarryOverTests(() => keepInCarryOvers);
2224
+ setTestObjects(testObjects => testObjects.concat(moveToTestObjects));
2225
+ }
2226
+
2227
+ /**
2228
+ * Stores test object inside suite state.
2229
+ * @param {VestTest} testObject
2230
+ */
2231
+
2232
+ var addTestToState = (testObject => {
2233
+ const [, setTestObjects] = useTestObjects();
2234
+ setTestObjects(testObjects => testObjects.concat(testObject));
2235
+ });
2236
+
2237
+ function isPromise(value) {
2238
+ return value && isFunction(value.then);
2239
+ }
2240
+
2241
+ function callEach(arr) {
2242
+ return arr.forEach(fn => fn());
2243
+ }
2244
+
2245
+ /**
2246
+ * Runs async test.
2247
+ * @param {VestTest} testObject A VestTest instance.
2248
+ */
2249
+
2250
+ const runAsyncTest = testObject => {
2251
+ const {
2252
+ asyncTest,
2253
+ statement
2254
+ } = testObject;
2255
+ const {
2256
+ stateRef
2257
+ } = context.use();
2258
+ const done = context.bind({
2259
+ stateRef
2260
+ }, () => {
2261
+ removePending(testObject); // This is for cases in which the suite state was already reset
2262
+
2263
+ if (testObject.canceled) {
2264
+ return;
2265
+ } // Perform required done callback calls and cleanups after the test is finished
2266
+
2267
+
2268
+ runDoneCallbacks(testObject.fieldName);
2269
+ });
2270
+ const fail = context.bind({
2271
+ stateRef
2272
+ }, rejectionMessage => {
2273
+ testObject.statement = isStringValue(rejectionMessage) ? rejectionMessage : statement;
2274
+ testObject.fail(); // Spreading the array to invalidate the cache
2275
+
2276
+ const [, setTestObjects] = useTestObjects();
2277
+ setTestObjects(testObjects => testObjects.slice());
2278
+ done();
2279
+ });
2280
+
2281
+ try {
2282
+ asyncTest.then(done, fail);
2283
+ } catch (e) {
2284
+ fail();
2285
+ }
2286
+ };
2287
+ /**
2288
+ * Runs done callback when async tests are finished running.
2289
+ * @param {string} [fieldName] Field name with associated callbacks.
2290
+ */
2291
+
2292
+
2293
+ const runDoneCallbacks = fieldName => {
2294
+ const [{
2295
+ fieldCallbacks,
2296
+ doneCallbacks
2297
+ }] = useTestCallbacks();
2298
+
2299
+ if (fieldName) {
2300
+ if (!hasRemainingTests(fieldName) && Array.isArray(fieldCallbacks[fieldName])) {
2301
+ callEach(fieldCallbacks[fieldName]);
2302
+ }
2303
+ }
2304
+
2305
+ if (!hasRemainingTests()) {
2306
+ callEach(doneCallbacks);
2307
+ }
2308
+ };
2309
+
2310
+ /**
2311
+ * Runs sync tests - or extracts promise.
2312
+ * @param {VestTest} testObject VestTest instance.
2313
+ * @returns {*} Result from test callback.
2314
+ */
2315
+
2316
+ function runSyncTest(testObject) {
2317
+ return context.run({
2318
+ currentTest: testObject
2319
+ }, () => {
2320
+ let result;
2321
+
2322
+ try {
2323
+ result = testObject.testFn();
2324
+ } catch (e) {
2325
+ if (isUndefined(testObject.statement) && isStringValue(e)) {
2326
+ testObject.statement = e;
2327
+ }
2328
+
2329
+ result = false;
2330
+ }
2331
+
2332
+ if (result === false) {
2333
+ testObject.fail();
2334
+ }
2335
+
2336
+ return result;
2337
+ });
2338
+ }
2339
+
2340
+ /**
2341
+ * Registers test, if async - adds to pending array
2342
+ * @param {VestTest} testObject A VestTest Instance.
2343
+ */
2344
+
2345
+ function registerTest(testObject) {
2346
+ addTestToState(testObject); // Run test callback.
2347
+ // If a promise is returned, set as async and
2348
+ // Move to pending list.
2349
+
2350
+ const result = runSyncTest(testObject);
2351
+
2352
+ try {
2353
+ // try catch for safe property access
2354
+ // in case object is an enforce chain
2355
+ if (isPromise(result)) {
2356
+ testObject.asyncTest = result;
2357
+ setPending(testObject);
2358
+ runAsyncTest(testObject);
2359
+ }
2360
+ } catch (_unused) {
2361
+ {
2362
+ throwError(`Your test function ${testObject.fieldName} returned ${JSON.stringify(result)}. Only "false" or a Promise are supported. Return values may cause unexpected behavior.`);
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+ /* eslint-disable jest/no-export */
2368
+
2369
+ function bindTestEach(test) {
2370
+ /**
2371
+ * Run multiple tests using a parameter table
2372
+ * @param {any[]} table Array of arrays with params for each run (will accept 1d-array and treat every item as size one array)
2373
+ * @param {String} fieldName Name of the field to test.
2374
+ * @param {String|function} [statement] The message returned in case of a failure. Follows printf syntax.
2375
+ * @param {function} testFn The actual test callback.
2376
+ * @return {VestTest[]} An array of VestTest instances.
2377
+ */
2378
+ function each(table) {
2379
+ if (!Array.isArray(table)) {
2380
+ throwError('test.each: Expected table to be an array.');
2381
+ }
2382
+
2383
+ return withArgs((fieldName, args) => {
2384
+ const [testFn, statement] = args.reverse();
2385
+ return table.map(item => {
2386
+ item = asArray(item);
2387
+ return test(optionalFunctionValue(fieldName, item), optionalFunctionValue(statement, item), () => testFn.apply(null, item));
2388
+ });
2389
+ });
2390
+ }
2391
+
2392
+ test.each = each;
2393
+ }
2394
+
2395
+ /* eslint-disable jest/no-export */
2396
+
2397
+ function bindTestMemo(test) {
2398
+ const cache = createCache(100); // arbitrary cache size
2399
+
2400
+ /**
2401
+ * Caches, or returns an already cached test call
2402
+ * @param {String} fieldName Name of the field to test.
2403
+ * @param {String} [statement] The message returned in case of a failure.
2404
+ * @param {function} testFn The actual test callback.
2405
+ * @param {any[]} deps Dependency array.
2406
+ * @return {VestTest} A VestTest instance.
2407
+ */
2408
+
2409
+ function memo(fieldName, args) {
2410
+ const [suiteId] = useSuiteId();
2411
+ const [deps, testFn, msg] = args.reverse(); // Implicit dependency for more specificity
2412
+
2413
+ const dependencies = [suiteId.id, fieldName].concat(deps);
2414
+ const cached = cache.get(dependencies);
2415
+
2416
+ if (isNull(cached)) {
2417
+ // Cache miss. Start fresh
2418
+ return cache(dependencies, test.bind(null, fieldName, msg, testFn));
2419
+ }
2420
+
2421
+ const [, testObject] = cached;
2422
+
2423
+ if (isExcluded(testObject)) {
2424
+ return testObject;
2425
+ }
2426
+
2427
+ addTestToState(testObject);
2428
+
2429
+ if (testObject && isPromise(testObject.asyncTest)) {
2430
+ setPending(testObject);
2431
+ runAsyncTest(testObject);
2432
+ }
2433
+
2434
+ return testObject;
2435
+ }
2436
+
2437
+ test.memo = withArgs(memo);
2438
+ }
2439
+
2440
+ /**
2441
+ * Test function used by consumer to provide their own validations.
2442
+ * @param {String} fieldName Name of the field to test.
2443
+ * @param {String} [statement] The message returned in case of a failure.
2444
+ * @param {function} testFn The actual test callback.
2445
+ * @return {VestTest} A VestTest instance.
2446
+ *
2447
+ * **IMPORTANT**
2448
+ * Changes to this function need to reflect in test.memo as well
2449
+ */
2450
+
2451
+ const test = withArgs(function (fieldName, args) {
2452
+ const [testFn, statement] = args.reverse();
2453
+ const [, setSkippedTests] = useSkippedTests();
2454
+ const {
2455
+ groupName
2456
+ } = context.use();
2457
+ const testObject = VestTest({
2458
+ fieldName,
2459
+ group: groupName,
2460
+ statement,
2461
+ testFn
2462
+ });
2463
+
2464
+ if (isExcluded(testObject)) {
2465
+ setSkippedTests(skippedTests => skippedTests.concat(testObject));
2466
+ mergeCarryOverTests(testObject);
2467
+ return testObject;
2468
+ }
2469
+
2470
+ if (!isFunction(testFn)) {
2471
+ return testObject;
2472
+ }
2473
+
2474
+ registerTest(testObject);
2475
+ return testObject;
2476
+ });
2477
+ bindTestEach(test);
2478
+ bindTestMemo(test);
2479
+
2480
+ const VERSION = "3.2.8";
2481
+ var vest = {
2482
+ VERSION,
2483
+ create: createSuite,
2484
+ enforce,
2485
+ group,
2486
+ only,
2487
+ optional,
2488
+ skip,
2489
+ skipWhen,
2490
+ test,
2491
+ warn
2492
+ };
2493
+
2494
+ module.exports = vest;