vest 4.1.3 → 4.2.1

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.
@@ -693,106 +693,100 @@
693
693
  return "".concat(n++);
694
694
  }; })(0);
695
695
 
696
- // eslint-disable-next-line max-lines-per-function
697
- function createState(onStateChange) {
698
- var state = {
699
- references: []
696
+ function shouldUseErrorAsMessage(message, error) {
697
+ // kind of cheating with this safe guard, but it does the job
698
+ return isUndefined(message) && isStringValue(error);
699
+ }
700
+
701
+ function asArray(possibleArg) {
702
+ return [].concat(possibleArg);
703
+ }
704
+
705
+ /**
706
+ * Creates a cache function
707
+ */
708
+ function createCache(maxSize) {
709
+ if (maxSize === void 0) { maxSize = 1; }
710
+ var cacheStorage = [];
711
+ var cache = function (deps, cacheAction) {
712
+ var cacheHit = cache.get(deps);
713
+ // cache hit is not null
714
+ if (cacheHit)
715
+ return cacheHit[1];
716
+ var result = cacheAction();
717
+ cacheStorage.unshift([deps.concat(), result]);
718
+ if (longerThan(cacheStorage, maxSize))
719
+ cacheStorage.length = maxSize;
720
+ return result;
700
721
  };
701
- var registrations = [];
702
- return {
703
- registerStateKey: registerStateKey,
704
- reset: reset
722
+ // invalidate an item in the cache by its dependencies
723
+ cache.invalidate = function (deps) {
724
+ var index = findIndex(deps);
725
+ if (index > -1)
726
+ cacheStorage.splice(index, 1);
705
727
  };
706
- /**
707
- * Registers a new key in the state, takes the initial value (may be a function that returns the initial value), returns a function.
708
- *
709
- * @example
710
- *
711
- * const useColor = state.registerStateKey("blue");
712
- *
713
- * let [color, setColor] = useColor(); // -> ["blue", Function]
714
- *
715
- * setColor("green");
716
- *
717
- * useColor()[0]; -> "green"
718
- */
719
- function registerStateKey(initialState, onUpdate) {
720
- var key = registrations.length;
721
- registrations.push([initialState, onUpdate]);
722
- return initKey(key, initialState);
723
- }
724
- function reset() {
725
- var prev = current();
726
- state.references = [];
727
- registrations.forEach(function (_a, index) {
728
- var initialValue = _a[0];
729
- return initKey(index, initialValue, prev[index]);
728
+ // Retrieves an item from the cache.
729
+ cache.get = function (deps) {
730
+ return cacheStorage[findIndex(deps)] || null;
731
+ };
732
+ return cache;
733
+ function findIndex(deps) {
734
+ return cacheStorage.findIndex(function (_a) {
735
+ var cachedDeps = _a[0];
736
+ return lengthEquals(deps, cachedDeps.length) &&
737
+ deps.every(function (dep, i) { return dep === cachedDeps[i]; });
730
738
  });
731
739
  }
732
- function initKey(key, initialState, prevState) {
733
- current().push();
734
- set(key, optionalFunctionValue(initialState, prevState));
735
- return function useStateKey() {
736
- return [
737
- current()[key],
738
- function (nextState) {
739
- return set(key, optionalFunctionValue(nextState, current()[key]));
740
- },
741
- ];
742
- };
743
- }
744
- function current() {
745
- return state.references;
746
- }
747
- function set(index, value) {
748
- var prevValue = state.references[index];
749
- state.references[index] = value;
750
- var _a = registrations[index], onUpdate = _a[1];
751
- if (isFunction(onUpdate)) {
752
- onUpdate(value, prevValue);
740
+ }
741
+
742
+ function last(values) {
743
+ var valuesArray = asArray(values);
744
+ return valuesArray[valuesArray.length - 1];
745
+ }
746
+
747
+ // This is kind of a map/filter in one function.
748
+ // Normally, behaves like a nested-array map,
749
+ // but returning `null` will drop the element from the array
750
+ function transform(array, cb) {
751
+ var res = [];
752
+ for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
753
+ var v = array_1[_i];
754
+ if (isArray(v)) {
755
+ res.push(transform(v, cb));
753
756
  }
754
- if (isFunction(onStateChange)) {
755
- onStateChange();
757
+ else {
758
+ var output = cb(v);
759
+ if (isNotNull(output)) {
760
+ res.push(output);
761
+ }
756
762
  }
757
763
  }
764
+ return res;
758
765
  }
759
-
760
- var IsolateTypes;
761
- (function (IsolateTypes) {
762
- IsolateTypes[IsolateTypes["DEFAULT"] = 0] = "DEFAULT";
763
- IsolateTypes[IsolateTypes["SUITE"] = 1] = "SUITE";
764
- IsolateTypes[IsolateTypes["EACH"] = 2] = "EACH";
765
- IsolateTypes[IsolateTypes["SKIP_WHEN"] = 3] = "SKIP_WHEN";
766
- IsolateTypes[IsolateTypes["OMIT_WHEN"] = 4] = "OMIT_WHEN";
767
- IsolateTypes[IsolateTypes["GROUP"] = 5] = "GROUP";
768
- })(IsolateTypes || (IsolateTypes = {}));
769
-
770
- function createStateRef(state, _a) {
771
- var suiteId = _a.suiteId, suiteName = _a.suiteName;
772
- return {
773
- optionalFields: state.registerStateKey(function () { return ({}); }),
774
- suiteId: state.registerStateKey(suiteId),
775
- suiteName: state.registerStateKey(suiteName),
776
- testCallbacks: state.registerStateKey(function () { return ({
777
- fieldCallbacks: {},
778
- doneCallbacks: []
779
- }); }),
780
- testObjects: state.registerStateKey(function (prev) {
781
- return {
782
- prev: prev ? prev.current : [],
783
- current: []
784
- };
785
- })
786
- };
766
+ function valueAtPath(array, path) {
767
+ return getCurrent(array, path)[last(path)];
787
768
  }
788
-
789
- function asArray(possibleArg) {
790
- return [].concat(possibleArg);
769
+ function setValueAtPath(array, path, value) {
770
+ var current = getCurrent(array, path);
771
+ current[last(path)] = value;
772
+ return array;
791
773
  }
792
-
793
- function last(values) {
794
- var valuesArray = asArray(values);
795
- return valuesArray[valuesArray.length - 1];
774
+ function flatten(values) {
775
+ return asArray(values).reduce(function (acc, value) {
776
+ if (isArray(value)) {
777
+ return acc.concat(flatten(value));
778
+ }
779
+ return asArray(acc).concat(value);
780
+ }, []);
781
+ }
782
+ function getCurrent(array, path) {
783
+ var current = array;
784
+ for (var _i = 0, _a = path.slice(0, -1); _i < _a.length; _i++) {
785
+ var p = _a[_i];
786
+ current[p] = defaultTo(current[p], []);
787
+ current = current[p];
788
+ }
789
+ return current;
796
790
  }
797
791
 
798
792
  function createCursor() {
@@ -829,6 +823,22 @@
829
823
  };
830
824
  }
831
825
 
826
+ var IsolateTypes;
827
+ (function (IsolateTypes) {
828
+ IsolateTypes[IsolateTypes["DEFAULT"] = 0] = "DEFAULT";
829
+ IsolateTypes[IsolateTypes["SUITE"] = 1] = "SUITE";
830
+ IsolateTypes[IsolateTypes["EACH"] = 2] = "EACH";
831
+ IsolateTypes[IsolateTypes["SKIP_WHEN"] = 3] = "SKIP_WHEN";
832
+ IsolateTypes[IsolateTypes["OMIT_WHEN"] = 4] = "OMIT_WHEN";
833
+ IsolateTypes[IsolateTypes["GROUP"] = 5] = "GROUP";
834
+ })(IsolateTypes || (IsolateTypes = {}));
835
+
836
+ var Modes;
837
+ (function (Modes) {
838
+ Modes[Modes["ALL"] = 0] = "ALL";
839
+ Modes[Modes["EAGER"] = 1] = "EAGER";
840
+ })(Modes || (Modes = {}));
841
+
832
842
  var context = createContext(function (ctxRef, parentContext) {
833
843
  return parentContext
834
844
  ? null
@@ -845,98 +855,11 @@
845
855
  prev: {}
846
856
  }
847
857
  },
858
+ mode: [Modes.ALL],
848
859
  testCursor: createCursor()
849
860
  }, ctxRef);
850
861
  });
851
862
 
852
- // This is sort of a map/filter in one function.
853
- // Normally, behaves like a nested-array map
854
- // Returning `null` will drop the element from the array
855
- function transform(array, cb) {
856
- var res = [];
857
- for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
858
- var v = array_1[_i];
859
- if (isArray(v)) {
860
- res.push(transform(v, cb));
861
- }
862
- else {
863
- var output = cb(v);
864
- if (isNotNull(output)) {
865
- res.push(output);
866
- }
867
- }
868
- }
869
- return res;
870
- }
871
- function valueAtPath(array, path) {
872
- return getCurrent(array, path)[last(path)];
873
- }
874
- function setValueAtPath(array, path, value) {
875
- var current = getCurrent(array, path);
876
- current[last(path)] = value;
877
- return array;
878
- }
879
- function flatten(values) {
880
- return asArray(values).reduce(function (acc, value) {
881
- if (isArray(value)) {
882
- return acc.concat(flatten(value));
883
- }
884
- return asArray(acc).concat(value);
885
- }, []);
886
- }
887
- function getCurrent(array, path) {
888
- var current = array;
889
- for (var _i = 0, _a = path.slice(0, -1); _i < _a.length; _i++) {
890
- var p = _a[_i];
891
- current[p] = defaultTo(current[p], []);
892
- current = current[p];
893
- }
894
- return current;
895
- }
896
-
897
- function shouldUseErrorAsMessage(message, error) {
898
- // kind of cheating with this safe guard, but it does the job
899
- return isUndefined(message) && isStringValue(error);
900
- }
901
-
902
- /**
903
- * Creates a cache function
904
- */
905
- function createCache(maxSize) {
906
- if (maxSize === void 0) { maxSize = 1; }
907
- var cacheStorage = [];
908
- var cache = function (deps, cacheAction) {
909
- var cacheHit = cache.get(deps);
910
- // cache hit is not null
911
- if (cacheHit)
912
- return cacheHit[1];
913
- var result = cacheAction();
914
- cacheStorage.unshift([deps.concat(), result]);
915
- if (longerThan(cacheStorage, maxSize))
916
- cacheStorage.length = maxSize;
917
- return result;
918
- };
919
- // invalidate an item in the cache by its dependencies
920
- cache.invalidate = function (deps) {
921
- var index = cacheStorage.findIndex(function (_a) {
922
- var cachedDeps = _a[0];
923
- return lengthEquals(deps, cachedDeps.length) &&
924
- deps.every(function (dep, i) { return dep === cachedDeps[i]; });
925
- });
926
- if (index > -1)
927
- cacheStorage.splice(index, 1);
928
- };
929
- // Retrieves an item from the cache.
930
- cache.get = function (deps) {
931
- return cacheStorage[cacheStorage.findIndex(function (_a) {
932
- var cachedDeps = _a[0];
933
- return lengthEquals(deps, cachedDeps.length) &&
934
- deps.every(function (dep, i) { return dep === cachedDeps[i]; });
935
- })] || null;
936
- };
937
- return cache;
938
- }
939
-
940
863
  // STATE REF
941
864
  function useStateRef() {
942
865
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -1152,6 +1075,89 @@
1152
1075
  var STATUS_CANCELED = 'CANCELED';
1153
1076
  var STATUS_OMITTED = 'OMITTED';
1154
1077
 
1078
+ // eslint-disable-next-line max-lines-per-function
1079
+ function createState(onStateChange) {
1080
+ var state = {
1081
+ references: []
1082
+ };
1083
+ var registrations = [];
1084
+ return {
1085
+ registerStateKey: registerStateKey,
1086
+ reset: reset
1087
+ };
1088
+ /**
1089
+ * Registers a new key in the state, takes the initial value (may be a function that returns the initial value), returns a function.
1090
+ *
1091
+ * @example
1092
+ *
1093
+ * const useColor = state.registerStateKey("blue");
1094
+ *
1095
+ * let [color, setColor] = useColor(); // -> ["blue", Function]
1096
+ *
1097
+ * setColor("green");
1098
+ *
1099
+ * useColor()[0]; -> "green"
1100
+ */
1101
+ function registerStateKey(initialState, onUpdate) {
1102
+ var key = registrations.length;
1103
+ registrations.push([initialState, onUpdate]);
1104
+ return initKey(key, initialState);
1105
+ }
1106
+ function reset() {
1107
+ var prev = current();
1108
+ state.references = [];
1109
+ registrations.forEach(function (_a, index) {
1110
+ var initialValue = _a[0];
1111
+ return initKey(index, initialValue, prev[index]);
1112
+ });
1113
+ }
1114
+ function initKey(key, initialState, prevState) {
1115
+ current().push();
1116
+ set(key, optionalFunctionValue(initialState, prevState));
1117
+ return function useStateKey() {
1118
+ return [
1119
+ current()[key],
1120
+ function (nextState) {
1121
+ return set(key, optionalFunctionValue(nextState, current()[key]));
1122
+ },
1123
+ ];
1124
+ };
1125
+ }
1126
+ function current() {
1127
+ return state.references;
1128
+ }
1129
+ function set(index, value) {
1130
+ var prevValue = state.references[index];
1131
+ state.references[index] = value;
1132
+ var _a = registrations[index], onUpdate = _a[1];
1133
+ if (isFunction(onUpdate)) {
1134
+ onUpdate(value, prevValue);
1135
+ }
1136
+ if (isFunction(onStateChange)) {
1137
+ onStateChange();
1138
+ }
1139
+ }
1140
+ }
1141
+
1142
+ function createStateRef(state, _a) {
1143
+ var suiteId = _a.suiteId, suiteName = _a.suiteName;
1144
+ return {
1145
+ optionalFields: state.registerStateKey(function () { return ({}); }),
1146
+ suiteId: state.registerStateKey(suiteId),
1147
+ suiteName: state.registerStateKey(suiteName),
1148
+ testCallbacks: state.registerStateKey(function () { return ({
1149
+ fieldCallbacks: {},
1150
+ doneCallbacks: []
1151
+ }); }),
1152
+ testObjects: state.registerStateKey(function (prev) {
1153
+ return {
1154
+ prev: prev ? prev.current : [],
1155
+ current: []
1156
+ };
1157
+ })
1158
+ };
1159
+ }
1160
+
1155
1161
  function usePath() {
1156
1162
  var context$1 = context.useX();
1157
1163
  return context$1.testCursor.getCursor();
@@ -1225,41 +1231,21 @@
1225
1231
  return context.useX().isolate.type === IsolateTypes.EACH;
1226
1232
  }
1227
1233
 
1228
- function nonMatchingFieldName(testObject, fieldName) {
1229
- return !!fieldName && !matchingFieldName(testObject, fieldName);
1230
- }
1231
- function matchingFieldName(testObject, fieldName) {
1232
- return !!(fieldName && testObject.fieldName === fieldName);
1233
- }
1234
-
1235
- /**
1236
- * Checks if a given field, or the suite as a whole still have remaining tests.
1237
- */
1238
- function hasRemainingTests(fieldName) {
1239
- var allIncomplete = useAllIncomplete();
1240
- if (isEmpty(allIncomplete)) {
1241
- return false;
1242
- }
1243
- if (fieldName) {
1244
- return allIncomplete.some(function (testObject) {
1245
- return matchingFieldName(testObject, fieldName);
1246
- });
1247
- }
1248
- return isNotEmpty(allIncomplete);
1249
- }
1234
+ var Severity;
1235
+ (function (Severity) {
1236
+ Severity["WARNINGS"] = "warnings";
1237
+ Severity["ERRORS"] = "errors";
1238
+ })(Severity || (Severity = {}));
1250
1239
 
1251
1240
  /**
1252
1241
  * Reads the testObjects list and gets full validation result from it.
1253
1242
  */
1254
1243
  function genTestsSummary() {
1255
1244
  var testObjects = useTestsFlat();
1256
- var summary = {
1257
- errorCount: 0,
1245
+ var summary = assign(baseStats(), {
1258
1246
  groups: {},
1259
- testCount: 0,
1260
- tests: {},
1261
- warnCount: 0
1262
- };
1247
+ tests: {}
1248
+ });
1263
1249
  appendSummary(testObjects);
1264
1250
  return countFailures(summary);
1265
1251
  function appendSummary(testObjects) {
@@ -1287,30 +1273,41 @@
1287
1273
  // eslint-disable-next-line max-statements
1288
1274
  function genTestObject(summaryKey, testObject) {
1289
1275
  var fieldName = testObject.fieldName, message = testObject.message;
1290
- summaryKey[fieldName] = summaryKey[fieldName] || {
1291
- errorCount: 0,
1292
- warnCount: 0,
1293
- testCount: 0
1294
- };
1276
+ summaryKey[fieldName] = summaryKey[fieldName] || baseStats();
1295
1277
  var testKey = summaryKey[fieldName];
1296
1278
  if (testObject.isNonActionable())
1297
1279
  return testKey;
1298
1280
  summaryKey[fieldName].testCount++;
1299
1281
  // Adds to severity group
1300
- function addTo(countKey, group) {
1282
+ function addTo(severity) {
1283
+ var countKey = severity === Severity.ERRORS ? 'errorCount' : 'warnCount';
1301
1284
  testKey[countKey]++;
1302
1285
  if (message) {
1303
- testKey[group] = (testKey[group] || []).concat(message);
1286
+ testKey[severity] = (testKey[severity] || []).concat(message);
1304
1287
  }
1305
1288
  }
1306
1289
  if (testObject.isFailing()) {
1307
- addTo('errorCount', 'errors');
1290
+ addTo(Severity.ERRORS);
1308
1291
  }
1309
1292
  else if (testObject.isWarning()) {
1310
- addTo('warnCount', 'warnings');
1293
+ addTo(Severity.WARNINGS);
1311
1294
  }
1312
1295
  return testKey;
1313
1296
  }
1297
+ function baseStats() {
1298
+ return {
1299
+ errorCount: 0,
1300
+ warnCount: 0,
1301
+ testCount: 0
1302
+ };
1303
+ }
1304
+
1305
+ function nonMatchingFieldName(testObject, fieldName) {
1306
+ return !!fieldName && !matchingFieldName(testObject, fieldName);
1307
+ }
1308
+ function matchingFieldName(testObject, fieldName) {
1309
+ return !!(fieldName && testObject.fieldName === fieldName);
1310
+ }
1314
1311
 
1315
1312
  function either(a, b) {
1316
1313
  return !!a !== !!b;
@@ -1320,7 +1317,7 @@
1320
1317
  * Checks that a given test object matches the currently specified severity level
1321
1318
  */
1322
1319
  function nonMatchingSeverityProfile(severity, testObject) {
1323
- return either(severity === 'warnings', testObject.warns());
1320
+ return either(severity === Severity.WARNINGS, testObject.warns());
1324
1321
  }
1325
1322
 
1326
1323
  function collectFailureMessages(severity, testObjects, options) {
@@ -1362,10 +1359,10 @@
1362
1359
  }
1363
1360
 
1364
1361
  function getErrors(fieldName) {
1365
- return getFailures('errors', fieldName);
1362
+ return getFailures(Severity.ERRORS, fieldName);
1366
1363
  }
1367
1364
  function getWarnings(fieldName) {
1368
- return getFailures('warnings', fieldName);
1365
+ return getFailures(Severity.WARNINGS, fieldName);
1369
1366
  }
1370
1367
  /**
1371
1368
  * @returns suite or field's errors or warnings.
@@ -1379,11 +1376,11 @@
1379
1376
  }
1380
1377
 
1381
1378
  function getErrorsByGroup(groupName, fieldName) {
1382
- var errors = getByGroup('errors', groupName, fieldName);
1379
+ var errors = getByGroup(Severity.ERRORS, groupName, fieldName);
1383
1380
  return getFailuresArrayOrObject(errors, fieldName);
1384
1381
  }
1385
1382
  function getWarningsByGroup(groupName, fieldName) {
1386
- var warnings = getByGroup('warnings', groupName, fieldName);
1383
+ var warnings = getByGroup(Severity.WARNINGS, groupName, fieldName);
1387
1384
  return getFailuresArrayOrObject(warnings, fieldName);
1388
1385
  }
1389
1386
  /**
@@ -1417,10 +1414,10 @@
1417
1414
  }
1418
1415
 
1419
1416
  function hasErrors(fieldName) {
1420
- return has('errors', fieldName);
1417
+ return has(Severity.ERRORS, fieldName);
1421
1418
  }
1422
1419
  function hasWarnings(fieldName) {
1423
- return has('warnings', fieldName);
1420
+ return has(Severity.WARNINGS, fieldName);
1424
1421
  }
1425
1422
  function has(severityKey, fieldName) {
1426
1423
  var testObjects = useTestsFlat();
@@ -1430,10 +1427,10 @@
1430
1427
  }
1431
1428
 
1432
1429
  function hasErrorsByGroup(groupName, fieldName) {
1433
- return hasByGroup('errors', groupName, fieldName);
1430
+ return hasByGroup(Severity.ERRORS, groupName, fieldName);
1434
1431
  }
1435
1432
  function hasWarningsByGroup(groupName, fieldName) {
1436
- return hasByGroup('warnings', groupName, fieldName);
1433
+ return hasByGroup(Severity.WARNINGS, groupName, fieldName);
1437
1434
  }
1438
1435
  /**
1439
1436
  * Checks whether there are failures in a given group.
@@ -1500,7 +1497,7 @@
1500
1497
  }
1501
1498
 
1502
1499
  var cache$1 = createCache(20);
1503
- function produceDraft() {
1500
+ function produceSuiteResult() {
1504
1501
  var testObjects = useTestsFlat();
1505
1502
  var ctxRef = { stateRef: useStateRef() };
1506
1503
  return cache$1([testObjects], context.bind(ctxRef, function () {
@@ -1515,19 +1512,35 @@
1515
1512
  hasWarnings: context.bind(ctxRef, hasWarnings),
1516
1513
  hasWarningsByGroup: context.bind(ctxRef, hasWarningsByGroup),
1517
1514
  isValid: context.bind(ctxRef, function (fieldName) {
1518
- return isValid(produceDraft(), fieldName);
1515
+ return isValid(produceSuiteResult(), fieldName);
1519
1516
  }),
1520
1517
  suiteName: suiteName
1521
1518
  });
1522
1519
  }));
1523
1520
  }
1524
1521
 
1522
+ /**
1523
+ * Checks if a given field, or the suite as a whole still have remaining tests.
1524
+ */
1525
+ function hasRemainingTests(fieldName) {
1526
+ var allIncomplete = useAllIncomplete();
1527
+ if (isEmpty(allIncomplete)) {
1528
+ return false;
1529
+ }
1530
+ if (fieldName) {
1531
+ return allIncomplete.some(function (testObject) {
1532
+ return matchingFieldName(testObject, fieldName);
1533
+ });
1534
+ }
1535
+ return isNotEmpty(allIncomplete);
1536
+ }
1537
+
1525
1538
  var cache = createCache(20);
1526
1539
  function produceFullResult() {
1527
1540
  var testObjects = useTestsFlat();
1528
1541
  var ctxRef = { stateRef: useStateRef() };
1529
1542
  return cache([testObjects], context.bind(ctxRef, function () {
1530
- return assign({}, produceDraft(), {
1543
+ return assign({}, produceSuiteResult(), {
1531
1544
  done: context.bind(ctxRef, done)
1532
1545
  });
1533
1546
  }));
@@ -1560,7 +1573,7 @@
1560
1573
  if (shouldSkipDoneRegistration(callback, fieldName, output)) {
1561
1574
  return output;
1562
1575
  }
1563
- var doneCallback = function () { return callback(produceDraft()); };
1576
+ var doneCallback = function () { return callback(produceSuiteResult()); };
1564
1577
  if (shouldRunDoneCallback(fieldName)) {
1565
1578
  doneCallback();
1566
1579
  return output;
@@ -1586,18 +1599,12 @@
1586
1599
  var listeners = {};
1587
1600
  return {
1588
1601
  emit: function (event, data) {
1589
- if (!listeners[event]) {
1590
- return;
1591
- }
1592
- listeners[event].forEach(function (listener) {
1593
- listener(data);
1602
+ (listeners[event] || []).forEach(function (handler) {
1603
+ handler(data);
1594
1604
  });
1595
1605
  },
1596
1606
  on: function (event, handler) {
1597
- if (!listeners[event]) {
1598
- listeners[event] = [];
1599
- }
1600
- listeners[event].push(handler);
1607
+ listeners[event] = (listeners[event] || []).concat(handler);
1601
1608
  return {
1602
1609
  off: function () {
1603
1610
  listeners[event] = listeners[event].filter(function (h) { return h !== handler; });
@@ -1665,9 +1672,7 @@
1665
1672
  */
1666
1673
  function runDoneCallbacks() {
1667
1674
  var doneCallbacks = useTestCallbacks()[0].doneCallbacks;
1668
- if (!hasRemainingTests()) {
1669
- callEach(doneCallbacks);
1670
- }
1675
+ callEach(doneCallbacks);
1671
1676
  }
1672
1677
 
1673
1678
  // eslint-disable-next-line max-lines-per-function
@@ -1681,14 +1686,21 @@
1681
1686
  }
1682
1687
  testObject.done();
1683
1688
  runFieldCallbacks(testObject.fieldName);
1684
- runDoneCallbacks();
1689
+ if (!hasRemainingTests()) {
1690
+ // When no more tests are running, emit the done event
1691
+ bus.emit(Events.ALL_RUNNING_TESTS_FINISHED);
1692
+ }
1685
1693
  });
1686
1694
  // Report that the suite completed its synchronous test run.
1687
1695
  // Async operations may still be running.
1688
- bus.on(Events.SUITE_COMPLETED, function () {
1696
+ bus.on(Events.SUITE_CALLBACK_DONE_RUNNING, function () {
1689
1697
  // Remove tests that are optional and need to be omitted
1690
1698
  omitOptionalTests();
1691
1699
  });
1700
+ // Called when all the tests, including async, are done running
1701
+ bus.on(Events.ALL_RUNNING_TESTS_FINISHED, function () {
1702
+ runDoneCallbacks();
1703
+ });
1692
1704
  // Removes a certain field from the state.
1693
1705
  bus.on(Events.REMOVE_FIELD, function (fieldName) {
1694
1706
  useEachTestObject(function (testObject) {
@@ -1718,9 +1730,10 @@
1718
1730
  var Events;
1719
1731
  (function (Events) {
1720
1732
  Events["TEST_COMPLETED"] = "test_completed";
1733
+ Events["ALL_RUNNING_TESTS_FINISHED"] = "all_running_tests_finished";
1721
1734
  Events["REMOVE_FIELD"] = "remove_field";
1722
1735
  Events["RESET_FIELD"] = "reset_field";
1723
- Events["SUITE_COMPLETED"] = "suite_completed";
1736
+ Events["SUITE_CALLBACK_DONE_RUNNING"] = "suite_callback_done_running";
1724
1737
  })(Events || (Events = {}));
1725
1738
 
1726
1739
  // eslint-disable-next-line max-lines-per-function
@@ -1757,11 +1770,11 @@
1757
1770
  });
1758
1771
  // Report the suite is done registering tests
1759
1772
  // Async tests may still be running
1760
- bus.emit(Events.SUITE_COMPLETED);
1773
+ bus.emit(Events.SUITE_CALLBACK_DONE_RUNNING);
1761
1774
  // Return the result
1762
1775
  return produceFullResult();
1763
1776
  }), {
1764
- get: context.bind(ctxRef, produceDraft),
1777
+ get: context.bind(ctxRef, produceSuiteResult),
1765
1778
  remove: context.bind(ctxRef, function (fieldName) {
1766
1779
  bus.emit(Events.REMOVE_FIELD, fieldName);
1767
1780
  }),
@@ -1788,7 +1801,7 @@
1788
1801
  */
1789
1802
  function each(list, callback) {
1790
1803
  if (!isFunction(callback)) {
1791
- throwError('callback must be a function');
1804
+ throwError('each callback must be a function');
1792
1805
  }
1793
1806
  isolate({ type: IsolateTypes.EACH }, function () {
1794
1807
  list.forEach(function (arg, index) {
@@ -1819,7 +1832,7 @@
1819
1832
  // we should skip the test if the parent conditional is true.
1820
1833
  isExcludedIndividually() ||
1821
1834
  // Otherwise, we should skip the test if the conditional is true.
1822
- optionalFunctionValue(conditional, optionalFunctionValue(produceDraft))
1835
+ optionalFunctionValue(conditional, optionalFunctionValue(produceSuiteResult))
1823
1836
  }, function () { return callback(); });
1824
1837
  });
1825
1838
  }
@@ -2019,11 +2032,50 @@
2019
2032
  if (isStringValue(condition)) {
2020
2033
  return Boolean(exclusion.tests[condition]);
2021
2034
  }
2022
- return optionalFunctionValue(condition, optionalFunctionValue(produceDraft));
2035
+ return optionalFunctionValue(condition, optionalFunctionValue(produceSuiteResult));
2023
2036
  };
2024
2037
  }
2025
2038
  }
2026
2039
 
2040
+ /**
2041
+ * Sets the suite to "eager" (fail fast) mode.
2042
+ * Eager mode will skip running subsequent tests of a failing fields.
2043
+ *
2044
+ * @example
2045
+ * // in the following example, the second test of username will not run
2046
+ * // if the first test of username failed.
2047
+ * const suite = create((data) => {
2048
+ * eager();
2049
+ *
2050
+ * test('username', 'username is required', () => {
2051
+ * enforce(data.username).isNotBlank();
2052
+ * });
2053
+ *
2054
+ * test('username', 'username is too short', () => {
2055
+ * enforce(data.username).longerThan(2);
2056
+ * });
2057
+ * });
2058
+ */
2059
+ function eager() {
2060
+ setMode(Modes.EAGER);
2061
+ }
2062
+ function shouldSkipBasedOnMode(testObject) {
2063
+ if (isEager() && hasErrors(testObject.fieldName))
2064
+ return true;
2065
+ return false;
2066
+ }
2067
+ function isEager() {
2068
+ return isMode(Modes.EAGER);
2069
+ }
2070
+ function isMode(mode) {
2071
+ var currentMode = context.useX().mode;
2072
+ return currentMode[0] === mode;
2073
+ }
2074
+ function setMode(nextMode) {
2075
+ var mode = context.useX().mode;
2076
+ mode[0] = nextMode;
2077
+ }
2078
+
2027
2079
  /**
2028
2080
  * Conditionally omits tests from the suite.
2029
2081
  *
@@ -2037,7 +2089,7 @@
2037
2089
  isolate({ type: IsolateTypes.OMIT_WHEN }, function () {
2038
2090
  context.run({
2039
2091
  omitted: isOmitted() ||
2040
- optionalFunctionValue(conditional, optionalFunctionValue(produceDraft))
2092
+ optionalFunctionValue(conditional, optionalFunctionValue(produceSuiteResult))
2041
2093
  }, function () { return callback(); });
2042
2094
  });
2043
2095
  }
@@ -2168,7 +2220,7 @@
2168
2220
  }
2169
2221
  }
2170
2222
  catch (e) {
2171
- throwError("Unexpected error encountered during test registration.\n Test Object: ".concat(testObject, ".\n Error: ").concat(e, "."));
2223
+ throwError("Unexpected error encountered during test registration.\n Test Object: ".concat(JSON.stringify(testObject), ".\n Error: ").concat(e, "."));
2172
2224
  }
2173
2225
  }
2174
2226
 
@@ -2254,6 +2306,11 @@
2254
2306
  // eslint-disable-next-line max-statements
2255
2307
  function registerPrevRunTest(testObject) {
2256
2308
  var prevRunTest = useTestAtCursor(testObject);
2309
+ if (shouldSkipBasedOnMode(testObject)) {
2310
+ moveForward();
2311
+ testObject.skip();
2312
+ return testObject;
2313
+ }
2257
2314
  if (isOmitted()) {
2258
2315
  prevRunTest.omit();
2259
2316
  moveForward();
@@ -2362,12 +2419,13 @@
2362
2419
  ctx.currentTest.warn();
2363
2420
  }
2364
2421
 
2365
- var VERSION = "4.1.3";
2422
+ var VERSION = "4.2.1";
2366
2423
 
2367
2424
  exports.VERSION = VERSION;
2368
2425
  exports.context = context;
2369
2426
  exports.create = create;
2370
2427
  exports.each = each;
2428
+ exports.eager = eager;
2371
2429
  exports.enforce = enforce;
2372
2430
  exports.group = group;
2373
2431
  exports.include = include;