vest 4.0.0-dev-cc5cf5 → 4.0.0-dev-31f012

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 (44) hide show
  1. package/CHANGELOG.md +52 -44
  2. package/README.md +7 -1
  3. package/dist/cjs/classnames.development.js +3 -3
  4. package/dist/cjs/classnames.production.js +1 -1
  5. package/dist/cjs/promisify.development.js +1 -1
  6. package/dist/cjs/promisify.production.js +1 -1
  7. package/dist/cjs/vest.development.js +394 -180
  8. package/dist/cjs/vest.production.js +1 -1
  9. package/dist/es/classnames.development.js +3 -3
  10. package/dist/es/classnames.production.js +1 -1
  11. package/dist/es/promisify.development.js +1 -1
  12. package/dist/es/promisify.production.js +1 -1
  13. package/dist/es/vest.development.js +394 -180
  14. package/dist/es/vest.production.js +1 -1
  15. package/dist/umd/classnames.development.js +3 -3
  16. package/dist/umd/classnames.production.js +1 -1
  17. package/dist/umd/promisify.development.js +1 -1
  18. package/dist/umd/promisify.production.js +1 -1
  19. package/dist/umd/vest.development.js +394 -180
  20. package/dist/umd/vest.production.js +1 -1
  21. package/package.json +1 -1
  22. package/testUtils/testObjects.ts +11 -2
  23. package/types/classnames.d.ts +2 -2
  24. package/types/vest.d.ts +10 -3
  25. package/docs/.nojekyll +0 -0
  26. package/docs/README.md +0 -107
  27. package/docs/_assets/favicon.ico +0 -0
  28. package/docs/_assets/vest-logo.png +0 -0
  29. package/docs/_sidebar.md +0 -14
  30. package/docs/cross_field_validations.md +0 -34
  31. package/docs/enforce.md +0 -11
  32. package/docs/exclusion.md +0 -129
  33. package/docs/getting_started.md +0 -72
  34. package/docs/group.md +0 -142
  35. package/docs/index.html +0 -41
  36. package/docs/migration.md +0 -202
  37. package/docs/n4s/rules.md +0 -1282
  38. package/docs/node.md +0 -36
  39. package/docs/optional.md +0 -103
  40. package/docs/result.md +0 -249
  41. package/docs/state.md +0 -102
  42. package/docs/test.md +0 -172
  43. package/docs/utilities.md +0 -109
  44. package/docs/warn.md +0 -82
@@ -228,8 +228,16 @@ function greaterThanOrEquals(value, gte) {
228
228
  return isNumeric(value) && isNumeric(gte) && Number(value) >= Number(gte);
229
229
  }
230
230
 
231
+ // The module is named "isArrayValue" since it
232
+ // is conflicting with a nested npm dependency.
233
+ // We may need to revisit this in the future.
234
+ function isArray(value) {
235
+ return Boolean(Array.isArray(value));
236
+ }
237
+ var isNotArray = bindNot(isArray);
238
+
231
239
  function inside(value, arg1) {
232
- if (Array.isArray(arg1)) {
240
+ if (isArray(arg1)) {
233
241
  return arg1.indexOf(value) !== -1;
234
242
  }
235
243
  // both value and arg1 are strings
@@ -240,14 +248,6 @@ function inside(value, arg1) {
240
248
  }
241
249
  var notInside = bindNot(inside);
242
250
 
243
- // The module is named "isArrayValue" since it
244
- // is conflicting with a nested npm dependency.
245
- // We may need to revisit this in the future.
246
- function isArray(value) {
247
- return Boolean(Array.isArray(value));
248
- }
249
- var isNotArray = bindNot(isArray);
250
-
251
251
  function lessThanOrEquals(value, lte) {
252
252
  return isNumeric(value) && isNumeric(lte) && Number(value) <= Number(lte);
253
253
  }
@@ -600,7 +600,7 @@ function eachEnforceRule(action) {
600
600
 
601
601
  function isProxySupported() {
602
602
  try {
603
- return typeof Proxy === 'function';
603
+ return isFunction(Proxy);
604
604
  }
605
605
  catch (_a) {
606
606
  return false;
@@ -621,7 +621,7 @@ function transformResult(result, ruleName, value) {
621
621
  return ruleReturn(result);
622
622
  }
623
623
  else {
624
- return ruleReturn(result.pass, optionalFunctionValue.apply(void 0, __spreadArray([result.message, ruleName, value], args, false)));
624
+ return ruleReturn(result.pass, optionalFunctionValue.apply(void 0, __spreadArray([result.message, ruleName, value], args)));
625
625
  }
626
626
  }
627
627
  function validateResult(result) {
@@ -655,9 +655,9 @@ function enforceEager(value) {
655
655
  for (var _i = 0; _i < arguments.length; _i++) {
656
656
  args[_i] = arguments[_i];
657
657
  }
658
- var transformedResult = transformResult.apply(void 0, __spreadArray([ctx$1.run({ value: value }, function () { return rule.apply(void 0, __spreadArray([value], args, false)); }),
658
+ var transformedResult = transformResult.apply(void 0, __spreadArray([ctx$1.run({ value: value }, function () { return rule.apply(void 0, __spreadArray([value], args)); }),
659
659
  ruleName,
660
- value], args, false));
660
+ value], args));
661
661
  if (!transformedResult.pass) {
662
662
  if (isEmpty(transformedResult.message)) {
663
663
  throwError("enforce/" + ruleName + " failed with " + JSON.stringify(value));
@@ -687,7 +687,7 @@ function genEnforceLazy(key) {
687
687
  }
688
688
  var rule = getRule(ruleName);
689
689
  registeredRules.push(function (value) {
690
- return transformResult.apply(void 0, __spreadArray([rule.apply(void 0, __spreadArray([value], args, false)), ruleName, value], args, false));
690
+ return transformResult.apply(void 0, __spreadArray([rule.apply(void 0, __spreadArray([value], args)), ruleName, value], args));
691
691
  });
692
692
  var proxy = {
693
693
  run: function (value) {
@@ -794,10 +794,6 @@ function genEnforce() {
794
794
  }
795
795
  var enforce = genEnforce();
796
796
 
797
- function asArray(possibleArg) {
798
- return [].concat(possibleArg);
799
- }
800
-
801
797
  /**
802
798
  * @returns a unique numeric id.
803
799
  */
@@ -834,15 +830,16 @@ function createState(onStateChange) {
834
830
  return initKey(key, initialState);
835
831
  }
836
832
  function reset() {
833
+ var prev = current();
837
834
  state.references = [];
838
835
  registrations.forEach(function (_a, index) {
839
836
  var initialValue = _a[0];
840
- return initKey(index, initialValue);
837
+ return initKey(index, initialValue, prev[index]);
841
838
  });
842
839
  }
843
- function initKey(key, initialState) {
840
+ function initKey(key, initialState, prevState) {
844
841
  current().push();
845
- set(key, optionalFunctionValue(initialState));
842
+ set(key, optionalFunctionValue(initialState, prevState));
846
843
  return function useStateKey() {
847
844
  return [
848
845
  current()[key],
@@ -868,18 +865,74 @@ function createState(onStateChange) {
868
865
  }
869
866
  }
870
867
 
868
+ var IsolateTypes;
869
+ (function (IsolateTypes) {
870
+ IsolateTypes[IsolateTypes["DEFAULT"] = 0] = "DEFAULT";
871
+ IsolateTypes[IsolateTypes["SUITE"] = 1] = "SUITE";
872
+ IsolateTypes[IsolateTypes["EACH"] = 2] = "EACH";
873
+ IsolateTypes[IsolateTypes["SKIP_WHEN"] = 3] = "SKIP_WHEN";
874
+ IsolateTypes[IsolateTypes["GROUP"] = 4] = "GROUP";
875
+ })(IsolateTypes || (IsolateTypes = {}));
876
+
871
877
  function createStateRef(state, _a) {
872
878
  var suiteId = _a.suiteId;
873
879
  return {
874
880
  optionalFields: state.registerStateKey(function () { return ({}); }),
875
- prevTestObjects: state.registerStateKey(function () { return []; }),
876
- suiteId: state.registerStateKey(function () { return suiteId; }),
881
+ suiteId: state.registerStateKey(suiteId),
877
882
  testCallbacks: state.registerStateKey(function () { return ({
878
883
  fieldCallbacks: {},
879
884
  doneCallbacks: []
880
885
  }); }),
881
- testObjects: state.registerStateKey(function () { return []; }),
882
- testObjectsCursor: state.registerStateKey(function () { return 0; })
886
+ testObjects: state.registerStateKey(function (prev) {
887
+ return {
888
+ prev: (prev ? prev.current : []),
889
+ current: []
890
+ };
891
+ })
892
+ };
893
+ }
894
+
895
+ function asArray(possibleArg) {
896
+ return [].concat(possibleArg);
897
+ }
898
+
899
+ function last(values) {
900
+ var valuesArray = asArray(values);
901
+ var _a = valuesArray, l = _a.length, _b = l - 1, lastValue = _a[_b];
902
+ return lastValue;
903
+ }
904
+
905
+ function createCursor() {
906
+ var storage = {
907
+ cursor: []
908
+ };
909
+ function addLevel() {
910
+ storage.cursor.push(0);
911
+ }
912
+ function removeLevel() {
913
+ storage.cursor.pop();
914
+ }
915
+ function cursorAt() {
916
+ return last(storage.cursor);
917
+ }
918
+ function getCursor() {
919
+ return asArray(storage.cursor);
920
+ }
921
+ function next() {
922
+ storage.cursor[storage.cursor.length - 1]++;
923
+ return last(storage.cursor);
924
+ }
925
+ function reset() {
926
+ storage.cursor = [0];
927
+ }
928
+ reset();
929
+ return {
930
+ addLevel: addLevel,
931
+ cursorAt: cursorAt,
932
+ getCursor: getCursor,
933
+ next: next,
934
+ removeLevel: removeLevel,
935
+ reset: reset
883
936
  };
884
937
  }
885
938
 
@@ -887,6 +940,8 @@ var ctx = createContext(function (ctxRef, parentContext) {
887
940
  return parentContext
888
941
  ? null
889
942
  : assign({}, {
943
+ isolate: { type: IsolateTypes.DEFAULT },
944
+ testCursor: createCursor(),
890
945
  exclusion: {
891
946
  tests: {},
892
947
  groups: {}
@@ -894,11 +949,49 @@ var ctx = createContext(function (ctxRef, parentContext) {
894
949
  }, ctxRef);
895
950
  });
896
951
 
897
- function nonMatchingFieldName(testObject, fieldName) {
898
- return !!fieldName && !matchingFieldName(testObject, fieldName);
952
+ // This is sort of a map/filter in one function.
953
+ // Normally, behaves like a nested-array map
954
+ // Returning `null` will drop the element from the array
955
+ function transform(array, cb) {
956
+ var res = [];
957
+ for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
958
+ var v = array_1[_i];
959
+ if (isArray(v)) {
960
+ res.push(transform(v, cb));
961
+ }
962
+ else {
963
+ var output = cb(v);
964
+ if (isNotNull(output)) {
965
+ res.push(output);
966
+ }
967
+ }
968
+ }
969
+ return res;
899
970
  }
900
- function matchingFieldName(testObject, fieldName) {
901
- return !!(fieldName && testObject.fieldName === fieldName);
971
+ function valueAtPath(array, path) {
972
+ return getCurrent(array, path)[last(path)];
973
+ }
974
+ function setValueAtPath(array, path, value) {
975
+ var current = getCurrent(array, path);
976
+ current[last(path)] = value;
977
+ return array;
978
+ }
979
+ function flatten(values) {
980
+ return asArray(values).reduce(function (acc, value) {
981
+ if (isArray(value)) {
982
+ return acc.concat(flatten(value));
983
+ }
984
+ return asArray(acc).concat(value);
985
+ }, []);
986
+ }
987
+ function getCurrent(array, path) {
988
+ var current = array;
989
+ for (var _i = 0, _a = path.slice(0, -1); _i < _a.length; _i++) {
990
+ var p = _a[_i];
991
+ current[p] = defaultTo(current[p], []);
992
+ current = current[p];
993
+ }
994
+ return current;
902
995
  }
903
996
 
904
997
  /**
@@ -957,89 +1050,101 @@ function useOptionalFields() {
957
1050
  function useTestObjects() {
958
1051
  return useStateRef().testObjects();
959
1052
  }
960
- function usePrevTestObjects() {
961
- return useStateRef().prevTestObjects();
962
- }
963
- function useCursorAt() {
964
- return useStateRef().testObjectsCursor();
965
- }
966
- function useSetNextCursorAt() {
967
- var _a = useCursorAt(), setCursorAt = _a[1];
968
- setCursorAt(function (cursorAt) { return cursorAt + 1; });
969
- }
970
1053
  // STATE ACTIONS
971
1054
  function useRefreshTestObjects() {
972
1055
  var _a = useTestObjects(), setTestObjects = _a[1];
973
- setTestObjects(function (testObjects) { return testObjects.slice(0); });
1056
+ setTestObjects(function (_a) {
1057
+ var current = _a.current, prev = _a.prev;
1058
+ return ({
1059
+ prev: prev,
1060
+ current: asArray(current)
1061
+ });
1062
+ });
974
1063
  }
975
- function useSetTestAtCursor(testObject) {
976
- var cursorAt = useCursorAt()[0];
977
- var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
978
- if (testObject === testObjects[cursorAt]) {
979
- return;
980
- }
981
- setTestObjects(function (testObjects) {
982
- var newTestsOrder = testObjects.slice(0);
983
- newTestsOrder[cursorAt] = testObject;
984
- return newTestsOrder;
1064
+ function useSetTests(handler) {
1065
+ var _a = useTestObjects(), testObjects = _a[1];
1066
+ testObjects(function (_a) {
1067
+ var current = _a.current, prev = _a.prev;
1068
+ return ({
1069
+ prev: prev,
1070
+ current: asArray(handler(current))
1071
+ });
985
1072
  });
986
1073
  }
987
- // DERIVED VALUES
988
- var omittedFieldsCache = createCache();
1074
+ // Derived state
1075
+ function useAllIncomplete() {
1076
+ var current = useTestObjects()[0].current;
1077
+ return flatten(transform(current, function (testObject) {
1078
+ return testObject.isPending() ? testObject : null;
1079
+ }));
1080
+ }
989
1081
  function useOmittedFields() {
990
- var testObjects = useTestObjects()[0];
991
- return omittedFieldsCache([testObjects], function () {
992
- return testObjects.reduce(function (omittedFields, testObject) {
993
- if (omittedFields[testObject.fieldName]) {
994
- return omittedFields;
995
- }
996
- if (testObject.isOmitted()) {
997
- omittedFields[testObject.fieldName] = true;
998
- }
1082
+ var testObjects = useTestsFlat();
1083
+ return testObjects.reduce(function (omittedFields, testObject) {
1084
+ if (omittedFields[testObject.fieldName]) {
999
1085
  return omittedFields;
1000
- }, {});
1001
- });
1086
+ }
1087
+ if (testObject.isOmitted()) {
1088
+ omittedFields[testObject.fieldName] = true;
1089
+ }
1090
+ return omittedFields;
1091
+ }, {});
1002
1092
  }
1003
- var incompleteCache = createCache();
1004
- function useAllIncomplete() {
1005
- var testObjects = useTestObjects()[0];
1006
- return incompleteCache([testObjects], function () {
1007
- return testObjects.filter(function (testObject) { return testObject.isPending(); });
1008
- });
1093
+ var flatCache = createCache();
1094
+ function useTestsFlat() {
1095
+ var current = useTestObjects()[0].current;
1096
+ return flatCache([current], function () { return flatten(current); });
1009
1097
  }
1010
1098
 
1011
- function omitOptionalTests() {
1012
- var _a = useTestObjects(), setTestObjects = _a[1];
1013
- var optionalFields = useOptionalFields()[0];
1014
- if (isEmpty(optionalFields)) {
1099
+ function usePath() {
1100
+ var context = ctx.useX();
1101
+ return context.testCursor.getCursor();
1102
+ }
1103
+ function useCursorAt() {
1104
+ var context = ctx.useX();
1105
+ return context.testCursor.cursorAt();
1106
+ }
1107
+ function moveForward() {
1108
+ var context = ctx.useX();
1109
+ return context.testCursor.next();
1110
+ }
1111
+ function addLevel() {
1112
+ var context = ctx.useX();
1113
+ context.testCursor.addLevel();
1114
+ }
1115
+ function removeLevel() {
1116
+ var context = ctx.useX();
1117
+ context.testCursor.removeLevel();
1118
+ }
1119
+
1120
+ function isolate(_a, callback) {
1121
+ var _b = _a.type, type = _b === void 0 ? IsolateTypes.DEFAULT : _b;
1122
+ if (!isFunction(callback)) {
1015
1123
  return;
1016
1124
  }
1017
- var shouldOmit = {};
1018
- setTestObjects(function (testObjects) {
1019
- return testObjects.map(function (testObject) {
1020
- var fieldName = testObject.fieldName;
1021
- if (shouldOmit.hasOwnProperty(fieldName)) {
1022
- omit(testObject);
1023
- }
1024
- else {
1025
- var optionalConfig = optionalFields[fieldName];
1026
- if (isFunction(optionalConfig)) {
1027
- shouldOmit[fieldName] = optionalConfig();
1028
- omit(testObject);
1029
- }
1030
- }
1031
- return testObject;
1032
- });
1125
+ var path = usePath();
1126
+ return ctx.run({ isolate: { type: type } }, function () {
1127
+ addLevel();
1128
+ useSetTests(function (tests) { return setValueAtPath(tests, path, []); });
1129
+ var res = callback();
1130
+ removeLevel();
1131
+ moveForward();
1132
+ return res;
1033
1133
  });
1034
- function omit(testObject) {
1035
- if (shouldOmit[testObject.fieldName]) {
1036
- testObject.omit();
1037
- }
1038
- }
1134
+ }
1135
+ function shouldAllowReorder() {
1136
+ return ctx.useX().isolate.type === IsolateTypes.EACH;
1137
+ }
1138
+
1139
+ function nonMatchingFieldName(testObject, fieldName) {
1140
+ return !!fieldName && !matchingFieldName(testObject, fieldName);
1141
+ }
1142
+ function matchingFieldName(testObject, fieldName) {
1143
+ return !!(fieldName && testObject.fieldName === fieldName);
1039
1144
  }
1040
1145
 
1041
1146
  /**
1042
- * Checks if a given tests, or the suite as a whole still have remaining tests.
1147
+ * Checks if a given field, or the suite as a whole still have remaining tests.
1043
1148
  */
1044
1149
  function hasRemainingTests(fieldName) {
1045
1150
  var allIncomplete = useAllIncomplete();
@@ -1058,7 +1163,7 @@ function hasRemainingTests(fieldName) {
1058
1163
  * Reads the testObjects list and gets full validation result from it.
1059
1164
  */
1060
1165
  function genTestsSummary() {
1061
- var testObjects = useTestObjects()[0];
1166
+ var testObjects = useTestsFlat();
1062
1167
  var summary = {
1063
1168
  errorCount: 0,
1064
1169
  groups: {},
@@ -1178,7 +1283,7 @@ function getWarnings(fieldName) {
1178
1283
  * @returns suite or field's errors or warnings.
1179
1284
  */
1180
1285
  function getFailures(severityKey, fieldName) {
1181
- var testObjects = useTestObjects()[0];
1286
+ var testObjects = useTestsFlat();
1182
1287
  var failureMessages = collectFailureMessages(severityKey, testObjects, {
1183
1288
  fieldName: fieldName
1184
1289
  });
@@ -1200,7 +1305,7 @@ function getByGroup(severityKey, group, fieldName) {
1200
1305
  if (!group) {
1201
1306
  throwError("get" + severityKey[0].toUpperCase() + severityKey.slice(1) + "ByGroup requires a group name. Received `" + group + "` instead.");
1202
1307
  }
1203
- var testObjects = useTestObjects()[0];
1308
+ var testObjects = useTestsFlat();
1204
1309
  var failureMessages = collectFailureMessages(severityKey, testObjects, {
1205
1310
  group: group,
1206
1311
  fieldName: fieldName
@@ -1231,7 +1336,7 @@ function hasWarnings(fieldName) {
1231
1336
  return has('warnings', fieldName);
1232
1337
  }
1233
1338
  function has(severityKey, fieldName) {
1234
- var testObjects = useTestObjects()[0];
1339
+ var testObjects = useTestsFlat();
1235
1340
  return testObjects.some(function (testObject) {
1236
1341
  return hasFailuresLogic(testObject, severityKey, fieldName);
1237
1342
  });
@@ -1247,7 +1352,7 @@ function hasWarningsByGroup(groupName, fieldName) {
1247
1352
  * Checks whether there are failures in a given group.
1248
1353
  */
1249
1354
  function hasByGroup(severityKey, group, fieldName) {
1250
- var testObjects = useTestObjects()[0];
1355
+ var testObjects = useTestsFlat();
1251
1356
  return testObjects.some(function (testObject) {
1252
1357
  return group === testObject.groupName
1253
1358
  ? hasFailuresLogic(testObject, severityKey, fieldName)
@@ -1263,7 +1368,7 @@ function isValid(result, fieldName) {
1263
1368
  if (result.hasErrors(fieldName)) {
1264
1369
  return false;
1265
1370
  }
1266
- var testObjects = useTestObjects()[0];
1371
+ var testObjects = useTestsFlat();
1267
1372
  if (isEmpty(testObjects)) {
1268
1373
  return false;
1269
1374
  }
@@ -1295,7 +1400,7 @@ function fieldDoesNotExist(result, fieldName) {
1295
1400
  return !!fieldName && isEmpty(result.tests[fieldName]);
1296
1401
  }
1297
1402
  function noMissingTests(fieldName) {
1298
- var testObjects = useTestObjects()[0];
1403
+ var testObjects = useTestsFlat();
1299
1404
  var optionalFields = useOptionalFields()[0];
1300
1405
  return testObjects.every(function (testObject) {
1301
1406
  if (nonMatchingFieldName(testObject, fieldName)) {
@@ -1309,7 +1414,7 @@ function noMissingTests(fieldName) {
1309
1414
 
1310
1415
  var cache$1 = createCache(20);
1311
1416
  function produceDraft() {
1312
- var testObjects = useTestObjects()[0];
1417
+ var testObjects = useTestsFlat();
1313
1418
  var ctxRef = { stateRef: useStateRef() };
1314
1419
  return cache$1([testObjects], ctx.bind(ctxRef, function () {
1315
1420
  return assign(genTestsSummary(), {
@@ -1330,7 +1435,7 @@ function produceDraft() {
1330
1435
 
1331
1436
  var cache = createCache(20);
1332
1437
  function produceFullResult() {
1333
- var testObjects = useTestObjects()[0];
1438
+ var testObjects = useTestsFlat();
1334
1439
  var ctxRef = { stateRef: useStateRef() };
1335
1440
  return cache([testObjects], ctx.bind(ctxRef, function () {
1336
1441
  return assign({}, produceDraft(), {
@@ -1413,6 +1518,44 @@ function createBus() {
1413
1518
  };
1414
1519
  }
1415
1520
 
1521
+ function omitOptionalTests() {
1522
+ var optionalFields = useOptionalFields()[0];
1523
+ if (isEmpty(optionalFields)) {
1524
+ return;
1525
+ }
1526
+ var shouldOmit = {};
1527
+ useSetTests(function (tests) {
1528
+ return transform(tests, function (testObject) {
1529
+ var fieldName = testObject.fieldName;
1530
+ if (shouldOmit.hasOwnProperty(fieldName)) {
1531
+ omit(testObject);
1532
+ }
1533
+ else {
1534
+ var optionalConfig = optionalFields[fieldName];
1535
+ if (isFunction(optionalConfig)) {
1536
+ shouldOmit[fieldName] = optionalConfig();
1537
+ omit(testObject);
1538
+ }
1539
+ }
1540
+ return testObject;
1541
+ });
1542
+ });
1543
+ function omit(testObject) {
1544
+ if (shouldOmit[testObject.fieldName]) {
1545
+ testObject.omit();
1546
+ }
1547
+ }
1548
+ }
1549
+
1550
+ /**
1551
+ * Removes test object from suite state
1552
+ */
1553
+ function removeTestFromState (testObject) {
1554
+ useSetTests(function (tests) {
1555
+ return transform(tests, function (test) { return (testObject !== test ? test : null); });
1556
+ });
1557
+ }
1558
+
1416
1559
  function callEach(arr) {
1417
1560
  return arr.forEach(function (fn) { return fn(); });
1418
1561
  }
@@ -1423,8 +1566,7 @@ function callEach(arr) {
1423
1566
  function runFieldCallbacks(fieldName) {
1424
1567
  var fieldCallbacks = useTestCallbacks()[0].fieldCallbacks;
1425
1568
  if (fieldName) {
1426
- if (!hasRemainingTests(fieldName) &&
1427
- Array.isArray(fieldCallbacks[fieldName])) {
1569
+ if (!hasRemainingTests(fieldName) && isArray(fieldCallbacks[fieldName])) {
1428
1570
  callEach(fieldCallbacks[fieldName]);
1429
1571
  }
1430
1572
  }
@@ -1441,6 +1583,8 @@ function runDoneCallbacks() {
1441
1583
 
1442
1584
  function initBus() {
1443
1585
  var bus = createBus();
1586
+ // Report a the completion of a test. There may be other tests with the same
1587
+ // name that are still running, or not yet started.
1444
1588
  bus.on(Events.TEST_COMPLETED, function (testObject) {
1445
1589
  if (testObject.isCanceled()) {
1446
1590
  return;
@@ -1449,6 +1593,22 @@ function initBus() {
1449
1593
  runFieldCallbacks(testObject.fieldName);
1450
1594
  runDoneCallbacks();
1451
1595
  });
1596
+ // Report that the suite completed its synchronous test run.
1597
+ // Async operations may still be running.
1598
+ bus.on(Events.SUITE_COMPLETED, function () {
1599
+ // Remove tests that are optional and need to be omitted
1600
+ omitOptionalTests();
1601
+ });
1602
+ // Removes a certain field from the state.
1603
+ bus.on(Events.REMOVE_FIELD, function (fieldName) {
1604
+ var testObjects = useTestsFlat();
1605
+ testObjects.forEach(function (testObject) {
1606
+ if (matchingFieldName(testObject, fieldName)) {
1607
+ testObject.cancel();
1608
+ removeTestFromState(testObject);
1609
+ }
1610
+ });
1611
+ });
1452
1612
  return bus;
1453
1613
  }
1454
1614
  function useBus() {
@@ -1461,42 +1621,48 @@ function useBus() {
1461
1621
  var Events;
1462
1622
  (function (Events) {
1463
1623
  Events["TEST_COMPLETED"] = "test_completed";
1624
+ Events["REMOVE_FIELD"] = "remove_field";
1625
+ Events["SUITE_COMPLETED"] = "suite_completed";
1464
1626
  })(Events || (Events = {}));
1465
1627
 
1466
1628
  // eslint-disable-next-line max-lines-per-function
1467
1629
  function create(suiteCallback) {
1468
1630
  if (!isFunction(suiteCallback)) {
1469
- throwError('Suite initialization error. Expected `tests` to be a function.');
1631
+ throwError('vest.create: Expected callback to be a function.');
1470
1632
  }
1633
+ // Event bus initialization
1471
1634
  var bus = initBus();
1635
+ // State initialization
1472
1636
  var state = createState();
1637
+ // State reference - this holds the actual state values
1473
1638
  var stateRef = createStateRef(state, { suiteId: genId() });
1474
- var suite = assign(ctx.bind({ stateRef: stateRef, bus: bus }, function () {
1639
+ // Create base context reference. All hooks will derive their data from this
1640
+ var ctxRef = { stateRef: stateRef, bus: bus };
1641
+ var suite = assign(
1642
+ // Bind the suite body to the context
1643
+ ctx.bind(ctxRef, function () {
1475
1644
  var args = [];
1476
1645
  for (var _i = 0; _i < arguments.length; _i++) {
1477
1646
  args[_i] = arguments[_i];
1478
1647
  }
1479
- var prevTestObjects = useTestObjects()[0];
1480
- var _a = usePrevTestObjects(), setPrevTestObjects = _a[1];
1648
+ // Reset the state. Migrates current test objects to `prev` array.
1481
1649
  state.reset();
1482
- setPrevTestObjects(function () { return prevTestObjects; });
1483
- // Run the consumer's callback
1484
- suiteCallback.apply(void 0, args);
1485
- omitOptionalTests();
1486
- var res = produceFullResult();
1487
- return res;
1650
+ // Create a top level isolate
1651
+ isolate({ type: IsolateTypes.SUITE }, function () {
1652
+ // Run the consumer's callback
1653
+ suiteCallback.apply(void 0, args);
1654
+ });
1655
+ // Report the suite is done registering tests
1656
+ // Async tests may still be running
1657
+ bus.emit(Events.SUITE_COMPLETED);
1658
+ // Return the result
1659
+ return produceFullResult();
1488
1660
  }), {
1489
- get: ctx.bind({ stateRef: stateRef }, produceDraft),
1490
- remove: ctx.bind({ stateRef: stateRef }, function (name) {
1491
- var testObjects = useTestObjects()[0];
1492
- // We're mutating the array in `cancel`, so we have to first copy it.
1493
- asArray(testObjects).forEach(function (testObject) {
1494
- if (matchingFieldName(testObject, name)) {
1495
- testObject.cancel();
1496
- }
1497
- });
1498
- }),
1499
- reset: state.reset
1661
+ get: ctx.bind(ctxRef, produceDraft),
1662
+ reset: state.reset,
1663
+ remove: ctx.bind(ctxRef, function (fieldName) {
1664
+ bus.emit(Events.REMOVE_FIELD, fieldName);
1665
+ })
1500
1666
  });
1501
1667
  return suite;
1502
1668
  }
@@ -1510,16 +1676,20 @@ var ERROR_HOOK_CALLED_OUTSIDE = 'hook called outside of a running suite.';
1510
1676
  * Adds a field or multiple fields to inclusion group.
1511
1677
  */
1512
1678
  function only(item) {
1513
- return addTo('only', 'tests', item);
1679
+ return addTo(0 /* ONLY */, 'tests', item);
1514
1680
  }
1515
- only.group = function (item) { return addTo('only', 'groups', item); };
1681
+ only.group = function (item) {
1682
+ return addTo(0 /* ONLY */, 'groups', item);
1683
+ };
1516
1684
  /**
1517
1685
  * Adds a field or multiple fields to exclusion group.
1518
1686
  */
1519
1687
  function skip(item) {
1520
- return addTo('skip', 'tests', item);
1688
+ return addTo(1 /* SKIP */, 'tests', item);
1521
1689
  }
1522
- skip.group = function (item) { return addTo('skip', 'groups', item); };
1690
+ skip.group = function (item) {
1691
+ return addTo(1 /* SKIP */, 'groups', item);
1692
+ };
1523
1693
  //Checks whether a certain test profile excluded by any of the exclusion groups.
1524
1694
  // eslint-disable-next-line complexity, max-statements
1525
1695
  function isExcluded(testObject) {
@@ -1591,7 +1761,8 @@ function addTo(exclusionGroup, itemType, item) {
1591
1761
  if (!isStringValue(itemName)) {
1592
1762
  return;
1593
1763
  }
1594
- context.exclusion[itemType][itemName] = exclusionGroup === 'only';
1764
+ context.exclusion[itemType][itemName] =
1765
+ exclusionGroup === 0 /* ONLY */;
1595
1766
  });
1596
1767
  }
1597
1768
  /**
@@ -1606,6 +1777,12 @@ function hasIncludedTests(keyTests) {
1606
1777
  return false;
1607
1778
  }
1608
1779
 
1780
+ function skipWhen(conditional, callback) {
1781
+ isolate({ type: IsolateTypes.SKIP_WHEN }, function () {
1782
+ ctx.run({ skipped: optionalFunctionValue(conditional) }, function () { return callback(); });
1783
+ });
1784
+ }
1785
+
1609
1786
  var ERROR_OUTSIDE_OF_TEST = "warn hook called outside of a test callback. It won't have an effect."
1610
1787
  ;
1611
1788
  /**
@@ -1619,10 +1796,6 @@ function warn() {
1619
1796
  ctx$1.currentTest.warn();
1620
1797
  }
1621
1798
 
1622
- function skipWhen(conditional, callback) {
1623
- ctx.run({ skipped: optionalFunctionValue(conditional) }, function () { return callback(); });
1624
- }
1625
-
1626
1799
  /**
1627
1800
  * Runs a group callback.
1628
1801
  */
@@ -1634,7 +1807,9 @@ function group(groupName, tests) {
1634
1807
  throwGroupError('callback must be a function');
1635
1808
  }
1636
1809
  // Running with the context applied
1637
- ctx.run({ groupName: groupName }, tests);
1810
+ isolate({ type: IsolateTypes.GROUP }, function () {
1811
+ ctx.run({ groupName: groupName }, tests);
1812
+ });
1638
1813
  }
1639
1814
  function throwGroupError(error) {
1640
1815
  throwError("Wrong arguments passed to group. Group " + error + ".");
@@ -1659,29 +1834,6 @@ function optional(optionals) {
1659
1834
  });
1660
1835
  }
1661
1836
 
1662
- /**
1663
- * Removes first found element from array
1664
- * WARNING: Mutates array
1665
- */
1666
- function removeElementFromArray(array, element) {
1667
- var index = array.indexOf(element);
1668
- if (index !== -1) {
1669
- array.splice(index, 1);
1670
- }
1671
- return array;
1672
- }
1673
-
1674
- /**
1675
- * Removes test object from suite state
1676
- */
1677
- function removeTestFromState (testObject) {
1678
- var _a = useTestObjects(), setTestObjects = _a[1];
1679
- setTestObjects(function (testObjects) {
1680
- // using asArray to clear the cache.
1681
- return asArray(removeElementFromArray(testObjects, testObject));
1682
- });
1683
- }
1684
-
1685
1837
  function shouldUseErrorAsMessage(message, error) {
1686
1838
  // kind of cheating with this safe guard, but it does the job
1687
1839
  return isUndefined(message) && isStringValue(error);
@@ -1743,11 +1895,17 @@ var VestTest = /** @class */ (function () {
1743
1895
  return this.hasFailures() || this.isCanceled() || this.isPassing();
1744
1896
  };
1745
1897
  VestTest.prototype.skip = function () {
1898
+ if (this.isPending()) {
1899
+ // Without this condition, the test will be marked as skipped even if it is pending.
1900
+ // This means that it will not be counted in "allIncomplete" and its done callbacks
1901
+ // will not be called, or will be called prematurely.
1902
+ return;
1903
+ }
1746
1904
  this.setStatus(STATUS_SKIPPED);
1747
1905
  };
1748
1906
  VestTest.prototype.cancel = function () {
1749
1907
  this.setStatus(STATUS_CANCELED);
1750
- removeTestFromState(this);
1908
+ useRefreshTestObjects();
1751
1909
  };
1752
1910
  VestTest.prototype.omit = function () {
1753
1911
  this.setStatus(STATUS_OMITTED);
@@ -1871,7 +2029,7 @@ function runSyncTest(testObject) {
1871
2029
  * Registers test, if async - adds to pending array
1872
2030
  */
1873
2031
  function registerTest(testObject) {
1874
- var emit = useBus().emit;
2032
+ var bus = useBus();
1875
2033
  // Run test callback.
1876
2034
  // If a promise is returned, set as async and
1877
2035
  // Move to pending list.
@@ -1885,36 +2043,89 @@ function registerTest(testObject) {
1885
2043
  runAsyncTest(testObject);
1886
2044
  }
1887
2045
  else {
1888
- emit(Events.TEST_COMPLETED, testObject);
2046
+ bus.emit(Events.TEST_COMPLETED, testObject);
1889
2047
  }
1890
2048
  }
1891
- catch (_a) {
1892
- throwError("Your test function " + testObject.fieldName + " returned " + JSON.stringify(result) + ". Only \"false\" or a Promise are supported. Return values may cause unexpected behavior.");
2049
+ catch (e) {
2050
+ throwError("Your test function " + testObject.fieldName + " returned a value. Only \"false\" or Promise returns are supported.");
1893
2051
  }
1894
2052
  }
1895
2053
 
1896
- function useTestAtCursor(initialValue) {
1897
- var cursorAt = useCursorAt()[0];
1898
- var prevTestObjects = usePrevTestObjects()[0];
1899
- if (isNotEmpty(prevTestObjects[cursorAt]) &&
1900
- !isSameProfileTest(prevTestObjects[cursorAt], initialValue)) {
1901
- throwErrorDeferred("Vest Critical Error: Tests called in different order than previous run.\n The test at cursor " + cursorAt + " was not the same profile as the previous test.\n expected: " + JSON.stringify(prevTestObjects[cursorAt]) + "\n actual: " + JSON.stringify(initialValue) + "\n This usually happens when you conditionally call your tests using if/else.\n This might lead to unexpected behavior in your test results.\n Replacing if/else with skipWhen solves these issues.");
2054
+ /**
2055
+ * This module serves as the "collision detection" mechanism for Vest.
2056
+ * It is used to ensure that tests are not called in a different order than
2057
+ * they were called in the previous run.
2058
+ * If they are, it will throw a deferred error unless explicitly allowed.
2059
+ *
2060
+ * For now it seems pretty safe, and it covers most common use cases, but it can
2061
+ * be improved in the future both in terms of performance and scenarios it covers.
2062
+ */
2063
+ // eslint-disable-next-line max-statements, max-lines-per-function
2064
+ function useTestAtCursor(newTestObject) {
2065
+ var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
2066
+ var prevTests = testObjects.prev;
2067
+ if (isEmpty(prevTests)) {
2068
+ useSetTestAtCursor(newTestObject);
2069
+ return newTestObject;
2070
+ }
2071
+ var prevTest = useGetTestAtCursor(prevTests);
2072
+ if (shouldPurgePrevTest(prevTest, newTestObject)) {
2073
+ throwTestOrderError(prevTest, newTestObject);
2074
+ // Here we handle just the omission of tests in the middle of the test suite.
2075
+ // We need to also handle a case in which tests are added in between other tests.
2076
+ // At the moment all we can do is just splice the tests out of the array when this happens.
2077
+ // A viable solution would be to use something like React's key prop to identify tests regardless
2078
+ // of their position in the suite. https://reactjs.org/docs/lists-and-keys.html#keys
2079
+ var current = getCurrent(prevTests, usePath());
2080
+ var cursorAt = useCursorAt();
2081
+ current.splice(cursorAt);
2082
+ // We actually don't mind mutating the state directly (as can be seen above). There is no harm in it
2083
+ // since we're only touching the "prev" state. The reason we still use the setter function is
2084
+ // to prevent future headaches if we ever do need to rely on prev-state immutability.
2085
+ setTestObjects(function (_a) {
2086
+ var current = _a.current;
2087
+ return ({
2088
+ prev: prevTests,
2089
+ current: current
2090
+ });
2091
+ });
2092
+ // Need to see if this has any effect at all.
2093
+ prevTest = null;
1902
2094
  }
1903
- var nextTest = defaultTo(prevTestObjects[cursorAt], initialValue);
2095
+ var nextTest = defaultTo(prevTest, newTestObject);
1904
2096
  useSetTestAtCursor(nextTest);
1905
2097
  return nextTest;
1906
2098
  }
2099
+ function useSetTestAtCursor(testObject) {
2100
+ var cursorPath = usePath();
2101
+ useSetTests(function (tests) {
2102
+ return setValueAtPath(tests, cursorPath, testObject);
2103
+ });
2104
+ }
2105
+ function useGetTestAtCursor(tests) {
2106
+ var cursorPath = usePath();
2107
+ return valueAtPath(tests, cursorPath);
2108
+ }
2109
+ function shouldPurgePrevTest(prevTest, newTest) {
2110
+ return isNotEmpty(prevTest) && !isSameProfileTest(prevTest, newTest);
2111
+ }
2112
+ function throwTestOrderError(prevTest, newTestObject) {
2113
+ if (shouldAllowReorder()) {
2114
+ return;
2115
+ }
2116
+ throwErrorDeferred("Vest Critical Error: Tests called in different order than previous run.\n expected: " + prevTest.fieldName + "\n received: " + newTestObject.fieldName + "\n This happens when you conditionally call your tests using if/else.\n This might lead to incorrect validation results.\n Replacing if/else with skipWhen solves these issues.");
2117
+ }
1907
2118
 
1908
2119
  function registerPrevRunTest(testObject) {
1909
2120
  var prevRunTest = useTestAtCursor(testObject);
1910
2121
  if (isExcluded(testObject)) {
1911
2122
  testObject.skip();
1912
- useSetNextCursorAt();
2123
+ moveForward();
1913
2124
  return prevRunTest;
1914
2125
  }
1915
2126
  cancelOverriddenPendingTest(prevRunTest, testObject);
1916
2127
  useSetTestAtCursor(testObject);
1917
- useSetNextCursorAt();
2128
+ moveForward();
1918
2129
  registerTestObjectByTier(testObject);
1919
2130
  return testObject;
1920
2131
  }
@@ -1933,7 +2144,7 @@ function bindTestEach(test) {
1933
2144
  * Run multiple tests using a parameter table
1934
2145
  */
1935
2146
  function each(table) {
1936
- if (!Array.isArray(table)) {
2147
+ if (!isArray(table)) {
1937
2148
  throwError('test.each: Expected table to be an array.');
1938
2149
  }
1939
2150
  function eachReturn(fieldName) {
@@ -1942,9 +2153,12 @@ function bindTestEach(test) {
1942
2153
  args[_i - 1] = arguments[_i];
1943
2154
  }
1944
2155
  var _a = args.reverse(), testFn = _a[0], message = _a[1];
1945
- return table.map(function (item) {
1946
- item = asArray(item);
1947
- return test(optionalFunctionValue.apply(void 0, __spreadArray([fieldName], item, false)), optionalFunctionValue.apply(void 0, __spreadArray([message], item, false)), function () { return testFn.apply(void 0, item); });
2156
+ return isolate({ type: IsolateTypes.EACH }, function () {
2157
+ return table.map(function (item) {
2158
+ item = asArray(item);
2159
+ return test(optionalFunctionValue.apply(void 0, __spreadArray([fieldName], item)), optionalFunctionValue.apply(void 0, __spreadArray([message], item)), function () { return testFn.apply(void 0, item); } // eslint-disable-line max-nested-callbacks
2160
+ );
2161
+ });
1948
2162
  });
1949
2163
  }
1950
2164
  return eachReturn;
@@ -1962,7 +2176,7 @@ function bindTestMemo(test) {
1962
2176
  args[_i - 1] = arguments[_i];
1963
2177
  }
1964
2178
  var suiteId = useSuiteId()[0];
1965
- var cursorAt = useCursorAt()[0];
2179
+ var cursorAt = useCursorAt();
1966
2180
  var _a = args.reverse(), deps = _a[0], testFn = _a[1], msg = _a[2];
1967
2181
  // Implicit dependency for more specificity
1968
2182
  var dependencies = [suiteId, fieldName, cursorAt].concat(deps);
@@ -1999,7 +2213,7 @@ var test = assign(testBase, {
1999
2213
  memo: bindTestMemo(testBase)
2000
2214
  });
2001
2215
 
2002
- var VERSION = "4.0.0-dev-cc5cf5";
2216
+ var VERSION = "4.0.0-dev-31f012";
2003
2217
 
2004
2218
  exports.VERSION = VERSION;
2005
2219
  exports.create = create;