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
@@ -224,8 +224,16 @@ function greaterThanOrEquals(value, gte) {
224
224
  return isNumeric(value) && isNumeric(gte) && Number(value) >= Number(gte);
225
225
  }
226
226
 
227
+ // The module is named "isArrayValue" since it
228
+ // is conflicting with a nested npm dependency.
229
+ // We may need to revisit this in the future.
230
+ function isArray(value) {
231
+ return Boolean(Array.isArray(value));
232
+ }
233
+ var isNotArray = bindNot(isArray);
234
+
227
235
  function inside(value, arg1) {
228
- if (Array.isArray(arg1)) {
236
+ if (isArray(arg1)) {
229
237
  return arg1.indexOf(value) !== -1;
230
238
  }
231
239
  // both value and arg1 are strings
@@ -236,14 +244,6 @@ function inside(value, arg1) {
236
244
  }
237
245
  var notInside = bindNot(inside);
238
246
 
239
- // The module is named "isArrayValue" since it
240
- // is conflicting with a nested npm dependency.
241
- // We may need to revisit this in the future.
242
- function isArray(value) {
243
- return Boolean(Array.isArray(value));
244
- }
245
- var isNotArray = bindNot(isArray);
246
-
247
247
  function lessThanOrEquals(value, lte) {
248
248
  return isNumeric(value) && isNumeric(lte) && Number(value) <= Number(lte);
249
249
  }
@@ -596,7 +596,7 @@ function eachEnforceRule(action) {
596
596
 
597
597
  function isProxySupported() {
598
598
  try {
599
- return typeof Proxy === 'function';
599
+ return isFunction(Proxy);
600
600
  }
601
601
  catch (_a) {
602
602
  return false;
@@ -617,7 +617,7 @@ function transformResult(result, ruleName, value) {
617
617
  return ruleReturn(result);
618
618
  }
619
619
  else {
620
- return ruleReturn(result.pass, optionalFunctionValue.apply(void 0, __spreadArray([result.message, ruleName, value], args, false)));
620
+ return ruleReturn(result.pass, optionalFunctionValue.apply(void 0, __spreadArray([result.message, ruleName, value], args)));
621
621
  }
622
622
  }
623
623
  function validateResult(result) {
@@ -651,9 +651,9 @@ function enforceEager(value) {
651
651
  for (var _i = 0; _i < arguments.length; _i++) {
652
652
  args[_i] = arguments[_i];
653
653
  }
654
- var transformedResult = transformResult.apply(void 0, __spreadArray([ctx$1.run({ value: value }, function () { return rule.apply(void 0, __spreadArray([value], args, false)); }),
654
+ var transformedResult = transformResult.apply(void 0, __spreadArray([ctx$1.run({ value: value }, function () { return rule.apply(void 0, __spreadArray([value], args)); }),
655
655
  ruleName,
656
- value], args, false));
656
+ value], args));
657
657
  if (!transformedResult.pass) {
658
658
  if (isEmpty(transformedResult.message)) {
659
659
  throwError("enforce/" + ruleName + " failed with " + JSON.stringify(value));
@@ -683,7 +683,7 @@ function genEnforceLazy(key) {
683
683
  }
684
684
  var rule = getRule(ruleName);
685
685
  registeredRules.push(function (value) {
686
- return transformResult.apply(void 0, __spreadArray([rule.apply(void 0, __spreadArray([value], args, false)), ruleName, value], args, false));
686
+ return transformResult.apply(void 0, __spreadArray([rule.apply(void 0, __spreadArray([value], args)), ruleName, value], args));
687
687
  });
688
688
  var proxy = {
689
689
  run: function (value) {
@@ -790,10 +790,6 @@ function genEnforce() {
790
790
  }
791
791
  var enforce = genEnforce();
792
792
 
793
- function asArray(possibleArg) {
794
- return [].concat(possibleArg);
795
- }
796
-
797
793
  /**
798
794
  * @returns a unique numeric id.
799
795
  */
@@ -830,15 +826,16 @@ function createState(onStateChange) {
830
826
  return initKey(key, initialState);
831
827
  }
832
828
  function reset() {
829
+ var prev = current();
833
830
  state.references = [];
834
831
  registrations.forEach(function (_a, index) {
835
832
  var initialValue = _a[0];
836
- return initKey(index, initialValue);
833
+ return initKey(index, initialValue, prev[index]);
837
834
  });
838
835
  }
839
- function initKey(key, initialState) {
836
+ function initKey(key, initialState, prevState) {
840
837
  current().push();
841
- set(key, optionalFunctionValue(initialState));
838
+ set(key, optionalFunctionValue(initialState, prevState));
842
839
  return function useStateKey() {
843
840
  return [
844
841
  current()[key],
@@ -864,18 +861,74 @@ function createState(onStateChange) {
864
861
  }
865
862
  }
866
863
 
864
+ var IsolateTypes;
865
+ (function (IsolateTypes) {
866
+ IsolateTypes[IsolateTypes["DEFAULT"] = 0] = "DEFAULT";
867
+ IsolateTypes[IsolateTypes["SUITE"] = 1] = "SUITE";
868
+ IsolateTypes[IsolateTypes["EACH"] = 2] = "EACH";
869
+ IsolateTypes[IsolateTypes["SKIP_WHEN"] = 3] = "SKIP_WHEN";
870
+ IsolateTypes[IsolateTypes["GROUP"] = 4] = "GROUP";
871
+ })(IsolateTypes || (IsolateTypes = {}));
872
+
867
873
  function createStateRef(state, _a) {
868
874
  var suiteId = _a.suiteId;
869
875
  return {
870
876
  optionalFields: state.registerStateKey(function () { return ({}); }),
871
- prevTestObjects: state.registerStateKey(function () { return []; }),
872
- suiteId: state.registerStateKey(function () { return suiteId; }),
877
+ suiteId: state.registerStateKey(suiteId),
873
878
  testCallbacks: state.registerStateKey(function () { return ({
874
879
  fieldCallbacks: {},
875
880
  doneCallbacks: []
876
881
  }); }),
877
- testObjects: state.registerStateKey(function () { return []; }),
878
- testObjectsCursor: state.registerStateKey(function () { return 0; })
882
+ testObjects: state.registerStateKey(function (prev) {
883
+ return {
884
+ prev: (prev ? prev.current : []),
885
+ current: []
886
+ };
887
+ })
888
+ };
889
+ }
890
+
891
+ function asArray(possibleArg) {
892
+ return [].concat(possibleArg);
893
+ }
894
+
895
+ function last(values) {
896
+ var valuesArray = asArray(values);
897
+ var _a = valuesArray, l = _a.length, _b = l - 1, lastValue = _a[_b];
898
+ return lastValue;
899
+ }
900
+
901
+ function createCursor() {
902
+ var storage = {
903
+ cursor: []
904
+ };
905
+ function addLevel() {
906
+ storage.cursor.push(0);
907
+ }
908
+ function removeLevel() {
909
+ storage.cursor.pop();
910
+ }
911
+ function cursorAt() {
912
+ return last(storage.cursor);
913
+ }
914
+ function getCursor() {
915
+ return asArray(storage.cursor);
916
+ }
917
+ function next() {
918
+ storage.cursor[storage.cursor.length - 1]++;
919
+ return last(storage.cursor);
920
+ }
921
+ function reset() {
922
+ storage.cursor = [0];
923
+ }
924
+ reset();
925
+ return {
926
+ addLevel: addLevel,
927
+ cursorAt: cursorAt,
928
+ getCursor: getCursor,
929
+ next: next,
930
+ removeLevel: removeLevel,
931
+ reset: reset
879
932
  };
880
933
  }
881
934
 
@@ -883,6 +936,8 @@ var ctx = createContext(function (ctxRef, parentContext) {
883
936
  return parentContext
884
937
  ? null
885
938
  : assign({}, {
939
+ isolate: { type: IsolateTypes.DEFAULT },
940
+ testCursor: createCursor(),
886
941
  exclusion: {
887
942
  tests: {},
888
943
  groups: {}
@@ -890,11 +945,49 @@ var ctx = createContext(function (ctxRef, parentContext) {
890
945
  }, ctxRef);
891
946
  });
892
947
 
893
- function nonMatchingFieldName(testObject, fieldName) {
894
- return !!fieldName && !matchingFieldName(testObject, fieldName);
948
+ // This is sort of a map/filter in one function.
949
+ // Normally, behaves like a nested-array map
950
+ // Returning `null` will drop the element from the array
951
+ function transform(array, cb) {
952
+ var res = [];
953
+ for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
954
+ var v = array_1[_i];
955
+ if (isArray(v)) {
956
+ res.push(transform(v, cb));
957
+ }
958
+ else {
959
+ var output = cb(v);
960
+ if (isNotNull(output)) {
961
+ res.push(output);
962
+ }
963
+ }
964
+ }
965
+ return res;
895
966
  }
896
- function matchingFieldName(testObject, fieldName) {
897
- return !!(fieldName && testObject.fieldName === fieldName);
967
+ function valueAtPath(array, path) {
968
+ return getCurrent(array, path)[last(path)];
969
+ }
970
+ function setValueAtPath(array, path, value) {
971
+ var current = getCurrent(array, path);
972
+ current[last(path)] = value;
973
+ return array;
974
+ }
975
+ function flatten(values) {
976
+ return asArray(values).reduce(function (acc, value) {
977
+ if (isArray(value)) {
978
+ return acc.concat(flatten(value));
979
+ }
980
+ return asArray(acc).concat(value);
981
+ }, []);
982
+ }
983
+ function getCurrent(array, path) {
984
+ var current = array;
985
+ for (var _i = 0, _a = path.slice(0, -1); _i < _a.length; _i++) {
986
+ var p = _a[_i];
987
+ current[p] = defaultTo(current[p], []);
988
+ current = current[p];
989
+ }
990
+ return current;
898
991
  }
899
992
 
900
993
  /**
@@ -953,89 +1046,101 @@ function useOptionalFields() {
953
1046
  function useTestObjects() {
954
1047
  return useStateRef().testObjects();
955
1048
  }
956
- function usePrevTestObjects() {
957
- return useStateRef().prevTestObjects();
958
- }
959
- function useCursorAt() {
960
- return useStateRef().testObjectsCursor();
961
- }
962
- function useSetNextCursorAt() {
963
- var _a = useCursorAt(), setCursorAt = _a[1];
964
- setCursorAt(function (cursorAt) { return cursorAt + 1; });
965
- }
966
1049
  // STATE ACTIONS
967
1050
  function useRefreshTestObjects() {
968
1051
  var _a = useTestObjects(), setTestObjects = _a[1];
969
- setTestObjects(function (testObjects) { return testObjects.slice(0); });
1052
+ setTestObjects(function (_a) {
1053
+ var current = _a.current, prev = _a.prev;
1054
+ return ({
1055
+ prev: prev,
1056
+ current: asArray(current)
1057
+ });
1058
+ });
970
1059
  }
971
- function useSetTestAtCursor(testObject) {
972
- var cursorAt = useCursorAt()[0];
973
- var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
974
- if (testObject === testObjects[cursorAt]) {
975
- return;
976
- }
977
- setTestObjects(function (testObjects) {
978
- var newTestsOrder = testObjects.slice(0);
979
- newTestsOrder[cursorAt] = testObject;
980
- return newTestsOrder;
1060
+ function useSetTests(handler) {
1061
+ var _a = useTestObjects(), testObjects = _a[1];
1062
+ testObjects(function (_a) {
1063
+ var current = _a.current, prev = _a.prev;
1064
+ return ({
1065
+ prev: prev,
1066
+ current: asArray(handler(current))
1067
+ });
981
1068
  });
982
1069
  }
983
- // DERIVED VALUES
984
- var omittedFieldsCache = createCache();
1070
+ // Derived state
1071
+ function useAllIncomplete() {
1072
+ var current = useTestObjects()[0].current;
1073
+ return flatten(transform(current, function (testObject) {
1074
+ return testObject.isPending() ? testObject : null;
1075
+ }));
1076
+ }
985
1077
  function useOmittedFields() {
986
- var testObjects = useTestObjects()[0];
987
- return omittedFieldsCache([testObjects], function () {
988
- return testObjects.reduce(function (omittedFields, testObject) {
989
- if (omittedFields[testObject.fieldName]) {
990
- return omittedFields;
991
- }
992
- if (testObject.isOmitted()) {
993
- omittedFields[testObject.fieldName] = true;
994
- }
1078
+ var testObjects = useTestsFlat();
1079
+ return testObjects.reduce(function (omittedFields, testObject) {
1080
+ if (omittedFields[testObject.fieldName]) {
995
1081
  return omittedFields;
996
- }, {});
997
- });
1082
+ }
1083
+ if (testObject.isOmitted()) {
1084
+ omittedFields[testObject.fieldName] = true;
1085
+ }
1086
+ return omittedFields;
1087
+ }, {});
998
1088
  }
999
- var incompleteCache = createCache();
1000
- function useAllIncomplete() {
1001
- var testObjects = useTestObjects()[0];
1002
- return incompleteCache([testObjects], function () {
1003
- return testObjects.filter(function (testObject) { return testObject.isPending(); });
1004
- });
1089
+ var flatCache = createCache();
1090
+ function useTestsFlat() {
1091
+ var current = useTestObjects()[0].current;
1092
+ return flatCache([current], function () { return flatten(current); });
1005
1093
  }
1006
1094
 
1007
- function omitOptionalTests() {
1008
- var _a = useTestObjects(), setTestObjects = _a[1];
1009
- var optionalFields = useOptionalFields()[0];
1010
- if (isEmpty(optionalFields)) {
1095
+ function usePath() {
1096
+ var context = ctx.useX();
1097
+ return context.testCursor.getCursor();
1098
+ }
1099
+ function useCursorAt() {
1100
+ var context = ctx.useX();
1101
+ return context.testCursor.cursorAt();
1102
+ }
1103
+ function moveForward() {
1104
+ var context = ctx.useX();
1105
+ return context.testCursor.next();
1106
+ }
1107
+ function addLevel() {
1108
+ var context = ctx.useX();
1109
+ context.testCursor.addLevel();
1110
+ }
1111
+ function removeLevel() {
1112
+ var context = ctx.useX();
1113
+ context.testCursor.removeLevel();
1114
+ }
1115
+
1116
+ function isolate(_a, callback) {
1117
+ var _b = _a.type, type = _b === void 0 ? IsolateTypes.DEFAULT : _b;
1118
+ if (!isFunction(callback)) {
1011
1119
  return;
1012
1120
  }
1013
- var shouldOmit = {};
1014
- setTestObjects(function (testObjects) {
1015
- return testObjects.map(function (testObject) {
1016
- var fieldName = testObject.fieldName;
1017
- if (shouldOmit.hasOwnProperty(fieldName)) {
1018
- omit(testObject);
1019
- }
1020
- else {
1021
- var optionalConfig = optionalFields[fieldName];
1022
- if (isFunction(optionalConfig)) {
1023
- shouldOmit[fieldName] = optionalConfig();
1024
- omit(testObject);
1025
- }
1026
- }
1027
- return testObject;
1028
- });
1121
+ var path = usePath();
1122
+ return ctx.run({ isolate: { type: type } }, function () {
1123
+ addLevel();
1124
+ useSetTests(function (tests) { return setValueAtPath(tests, path, []); });
1125
+ var res = callback();
1126
+ removeLevel();
1127
+ moveForward();
1128
+ return res;
1029
1129
  });
1030
- function omit(testObject) {
1031
- if (shouldOmit[testObject.fieldName]) {
1032
- testObject.omit();
1033
- }
1034
- }
1130
+ }
1131
+ function shouldAllowReorder() {
1132
+ return ctx.useX().isolate.type === IsolateTypes.EACH;
1133
+ }
1134
+
1135
+ function nonMatchingFieldName(testObject, fieldName) {
1136
+ return !!fieldName && !matchingFieldName(testObject, fieldName);
1137
+ }
1138
+ function matchingFieldName(testObject, fieldName) {
1139
+ return !!(fieldName && testObject.fieldName === fieldName);
1035
1140
  }
1036
1141
 
1037
1142
  /**
1038
- * Checks if a given tests, or the suite as a whole still have remaining tests.
1143
+ * Checks if a given field, or the suite as a whole still have remaining tests.
1039
1144
  */
1040
1145
  function hasRemainingTests(fieldName) {
1041
1146
  var allIncomplete = useAllIncomplete();
@@ -1054,7 +1159,7 @@ function hasRemainingTests(fieldName) {
1054
1159
  * Reads the testObjects list and gets full validation result from it.
1055
1160
  */
1056
1161
  function genTestsSummary() {
1057
- var testObjects = useTestObjects()[0];
1162
+ var testObjects = useTestsFlat();
1058
1163
  var summary = {
1059
1164
  errorCount: 0,
1060
1165
  groups: {},
@@ -1174,7 +1279,7 @@ function getWarnings(fieldName) {
1174
1279
  * @returns suite or field's errors or warnings.
1175
1280
  */
1176
1281
  function getFailures(severityKey, fieldName) {
1177
- var testObjects = useTestObjects()[0];
1282
+ var testObjects = useTestsFlat();
1178
1283
  var failureMessages = collectFailureMessages(severityKey, testObjects, {
1179
1284
  fieldName: fieldName
1180
1285
  });
@@ -1196,7 +1301,7 @@ function getByGroup(severityKey, group, fieldName) {
1196
1301
  if (!group) {
1197
1302
  throwError("get" + severityKey[0].toUpperCase() + severityKey.slice(1) + "ByGroup requires a group name. Received `" + group + "` instead.");
1198
1303
  }
1199
- var testObjects = useTestObjects()[0];
1304
+ var testObjects = useTestsFlat();
1200
1305
  var failureMessages = collectFailureMessages(severityKey, testObjects, {
1201
1306
  group: group,
1202
1307
  fieldName: fieldName
@@ -1227,7 +1332,7 @@ function hasWarnings(fieldName) {
1227
1332
  return has('warnings', fieldName);
1228
1333
  }
1229
1334
  function has(severityKey, fieldName) {
1230
- var testObjects = useTestObjects()[0];
1335
+ var testObjects = useTestsFlat();
1231
1336
  return testObjects.some(function (testObject) {
1232
1337
  return hasFailuresLogic(testObject, severityKey, fieldName);
1233
1338
  });
@@ -1243,7 +1348,7 @@ function hasWarningsByGroup(groupName, fieldName) {
1243
1348
  * Checks whether there are failures in a given group.
1244
1349
  */
1245
1350
  function hasByGroup(severityKey, group, fieldName) {
1246
- var testObjects = useTestObjects()[0];
1351
+ var testObjects = useTestsFlat();
1247
1352
  return testObjects.some(function (testObject) {
1248
1353
  return group === testObject.groupName
1249
1354
  ? hasFailuresLogic(testObject, severityKey, fieldName)
@@ -1259,7 +1364,7 @@ function isValid(result, fieldName) {
1259
1364
  if (result.hasErrors(fieldName)) {
1260
1365
  return false;
1261
1366
  }
1262
- var testObjects = useTestObjects()[0];
1367
+ var testObjects = useTestsFlat();
1263
1368
  if (isEmpty(testObjects)) {
1264
1369
  return false;
1265
1370
  }
@@ -1291,7 +1396,7 @@ function fieldDoesNotExist(result, fieldName) {
1291
1396
  return !!fieldName && isEmpty(result.tests[fieldName]);
1292
1397
  }
1293
1398
  function noMissingTests(fieldName) {
1294
- var testObjects = useTestObjects()[0];
1399
+ var testObjects = useTestsFlat();
1295
1400
  var optionalFields = useOptionalFields()[0];
1296
1401
  return testObjects.every(function (testObject) {
1297
1402
  if (nonMatchingFieldName(testObject, fieldName)) {
@@ -1305,7 +1410,7 @@ function noMissingTests(fieldName) {
1305
1410
 
1306
1411
  var cache$1 = createCache(20);
1307
1412
  function produceDraft() {
1308
- var testObjects = useTestObjects()[0];
1413
+ var testObjects = useTestsFlat();
1309
1414
  var ctxRef = { stateRef: useStateRef() };
1310
1415
  return cache$1([testObjects], ctx.bind(ctxRef, function () {
1311
1416
  return assign(genTestsSummary(), {
@@ -1326,7 +1431,7 @@ function produceDraft() {
1326
1431
 
1327
1432
  var cache = createCache(20);
1328
1433
  function produceFullResult() {
1329
- var testObjects = useTestObjects()[0];
1434
+ var testObjects = useTestsFlat();
1330
1435
  var ctxRef = { stateRef: useStateRef() };
1331
1436
  return cache([testObjects], ctx.bind(ctxRef, function () {
1332
1437
  return assign({}, produceDraft(), {
@@ -1409,6 +1514,44 @@ function createBus() {
1409
1514
  };
1410
1515
  }
1411
1516
 
1517
+ function omitOptionalTests() {
1518
+ var optionalFields = useOptionalFields()[0];
1519
+ if (isEmpty(optionalFields)) {
1520
+ return;
1521
+ }
1522
+ var shouldOmit = {};
1523
+ useSetTests(function (tests) {
1524
+ return transform(tests, function (testObject) {
1525
+ var fieldName = testObject.fieldName;
1526
+ if (shouldOmit.hasOwnProperty(fieldName)) {
1527
+ omit(testObject);
1528
+ }
1529
+ else {
1530
+ var optionalConfig = optionalFields[fieldName];
1531
+ if (isFunction(optionalConfig)) {
1532
+ shouldOmit[fieldName] = optionalConfig();
1533
+ omit(testObject);
1534
+ }
1535
+ }
1536
+ return testObject;
1537
+ });
1538
+ });
1539
+ function omit(testObject) {
1540
+ if (shouldOmit[testObject.fieldName]) {
1541
+ testObject.omit();
1542
+ }
1543
+ }
1544
+ }
1545
+
1546
+ /**
1547
+ * Removes test object from suite state
1548
+ */
1549
+ function removeTestFromState (testObject) {
1550
+ useSetTests(function (tests) {
1551
+ return transform(tests, function (test) { return (testObject !== test ? test : null); });
1552
+ });
1553
+ }
1554
+
1412
1555
  function callEach(arr) {
1413
1556
  return arr.forEach(function (fn) { return fn(); });
1414
1557
  }
@@ -1419,8 +1562,7 @@ function callEach(arr) {
1419
1562
  function runFieldCallbacks(fieldName) {
1420
1563
  var fieldCallbacks = useTestCallbacks()[0].fieldCallbacks;
1421
1564
  if (fieldName) {
1422
- if (!hasRemainingTests(fieldName) &&
1423
- Array.isArray(fieldCallbacks[fieldName])) {
1565
+ if (!hasRemainingTests(fieldName) && isArray(fieldCallbacks[fieldName])) {
1424
1566
  callEach(fieldCallbacks[fieldName]);
1425
1567
  }
1426
1568
  }
@@ -1437,6 +1579,8 @@ function runDoneCallbacks() {
1437
1579
 
1438
1580
  function initBus() {
1439
1581
  var bus = createBus();
1582
+ // Report a the completion of a test. There may be other tests with the same
1583
+ // name that are still running, or not yet started.
1440
1584
  bus.on(Events.TEST_COMPLETED, function (testObject) {
1441
1585
  if (testObject.isCanceled()) {
1442
1586
  return;
@@ -1445,6 +1589,22 @@ function initBus() {
1445
1589
  runFieldCallbacks(testObject.fieldName);
1446
1590
  runDoneCallbacks();
1447
1591
  });
1592
+ // Report that the suite completed its synchronous test run.
1593
+ // Async operations may still be running.
1594
+ bus.on(Events.SUITE_COMPLETED, function () {
1595
+ // Remove tests that are optional and need to be omitted
1596
+ omitOptionalTests();
1597
+ });
1598
+ // Removes a certain field from the state.
1599
+ bus.on(Events.REMOVE_FIELD, function (fieldName) {
1600
+ var testObjects = useTestsFlat();
1601
+ testObjects.forEach(function (testObject) {
1602
+ if (matchingFieldName(testObject, fieldName)) {
1603
+ testObject.cancel();
1604
+ removeTestFromState(testObject);
1605
+ }
1606
+ });
1607
+ });
1448
1608
  return bus;
1449
1609
  }
1450
1610
  function useBus() {
@@ -1457,42 +1617,48 @@ function useBus() {
1457
1617
  var Events;
1458
1618
  (function (Events) {
1459
1619
  Events["TEST_COMPLETED"] = "test_completed";
1620
+ Events["REMOVE_FIELD"] = "remove_field";
1621
+ Events["SUITE_COMPLETED"] = "suite_completed";
1460
1622
  })(Events || (Events = {}));
1461
1623
 
1462
1624
  // eslint-disable-next-line max-lines-per-function
1463
1625
  function create(suiteCallback) {
1464
1626
  if (!isFunction(suiteCallback)) {
1465
- throwError('Suite initialization error. Expected `tests` to be a function.');
1627
+ throwError('vest.create: Expected callback to be a function.');
1466
1628
  }
1629
+ // Event bus initialization
1467
1630
  var bus = initBus();
1631
+ // State initialization
1468
1632
  var state = createState();
1633
+ // State reference - this holds the actual state values
1469
1634
  var stateRef = createStateRef(state, { suiteId: genId() });
1470
- var suite = assign(ctx.bind({ stateRef: stateRef, bus: bus }, function () {
1635
+ // Create base context reference. All hooks will derive their data from this
1636
+ var ctxRef = { stateRef: stateRef, bus: bus };
1637
+ var suite = assign(
1638
+ // Bind the suite body to the context
1639
+ ctx.bind(ctxRef, function () {
1471
1640
  var args = [];
1472
1641
  for (var _i = 0; _i < arguments.length; _i++) {
1473
1642
  args[_i] = arguments[_i];
1474
1643
  }
1475
- var prevTestObjects = useTestObjects()[0];
1476
- var _a = usePrevTestObjects(), setPrevTestObjects = _a[1];
1644
+ // Reset the state. Migrates current test objects to `prev` array.
1477
1645
  state.reset();
1478
- setPrevTestObjects(function () { return prevTestObjects; });
1479
- // Run the consumer's callback
1480
- suiteCallback.apply(void 0, args);
1481
- omitOptionalTests();
1482
- var res = produceFullResult();
1483
- return res;
1646
+ // Create a top level isolate
1647
+ isolate({ type: IsolateTypes.SUITE }, function () {
1648
+ // Run the consumer's callback
1649
+ suiteCallback.apply(void 0, args);
1650
+ });
1651
+ // Report the suite is done registering tests
1652
+ // Async tests may still be running
1653
+ bus.emit(Events.SUITE_COMPLETED);
1654
+ // Return the result
1655
+ return produceFullResult();
1484
1656
  }), {
1485
- get: ctx.bind({ stateRef: stateRef }, produceDraft),
1486
- remove: ctx.bind({ stateRef: stateRef }, function (name) {
1487
- var testObjects = useTestObjects()[0];
1488
- // We're mutating the array in `cancel`, so we have to first copy it.
1489
- asArray(testObjects).forEach(function (testObject) {
1490
- if (matchingFieldName(testObject, name)) {
1491
- testObject.cancel();
1492
- }
1493
- });
1494
- }),
1495
- reset: state.reset
1657
+ get: ctx.bind(ctxRef, produceDraft),
1658
+ reset: state.reset,
1659
+ remove: ctx.bind(ctxRef, function (fieldName) {
1660
+ bus.emit(Events.REMOVE_FIELD, fieldName);
1661
+ })
1496
1662
  });
1497
1663
  return suite;
1498
1664
  }
@@ -1506,16 +1672,20 @@ var ERROR_HOOK_CALLED_OUTSIDE = 'hook called outside of a running suite.';
1506
1672
  * Adds a field or multiple fields to inclusion group.
1507
1673
  */
1508
1674
  function only(item) {
1509
- return addTo('only', 'tests', item);
1675
+ return addTo(0 /* ONLY */, 'tests', item);
1510
1676
  }
1511
- only.group = function (item) { return addTo('only', 'groups', item); };
1677
+ only.group = function (item) {
1678
+ return addTo(0 /* ONLY */, 'groups', item);
1679
+ };
1512
1680
  /**
1513
1681
  * Adds a field or multiple fields to exclusion group.
1514
1682
  */
1515
1683
  function skip(item) {
1516
- return addTo('skip', 'tests', item);
1684
+ return addTo(1 /* SKIP */, 'tests', item);
1517
1685
  }
1518
- skip.group = function (item) { return addTo('skip', 'groups', item); };
1686
+ skip.group = function (item) {
1687
+ return addTo(1 /* SKIP */, 'groups', item);
1688
+ };
1519
1689
  //Checks whether a certain test profile excluded by any of the exclusion groups.
1520
1690
  // eslint-disable-next-line complexity, max-statements
1521
1691
  function isExcluded(testObject) {
@@ -1587,7 +1757,8 @@ function addTo(exclusionGroup, itemType, item) {
1587
1757
  if (!isStringValue(itemName)) {
1588
1758
  return;
1589
1759
  }
1590
- context.exclusion[itemType][itemName] = exclusionGroup === 'only';
1760
+ context.exclusion[itemType][itemName] =
1761
+ exclusionGroup === 0 /* ONLY */;
1591
1762
  });
1592
1763
  }
1593
1764
  /**
@@ -1602,6 +1773,12 @@ function hasIncludedTests(keyTests) {
1602
1773
  return false;
1603
1774
  }
1604
1775
 
1776
+ function skipWhen(conditional, callback) {
1777
+ isolate({ type: IsolateTypes.SKIP_WHEN }, function () {
1778
+ ctx.run({ skipped: optionalFunctionValue(conditional) }, function () { return callback(); });
1779
+ });
1780
+ }
1781
+
1605
1782
  var ERROR_OUTSIDE_OF_TEST = "warn hook called outside of a test callback. It won't have an effect."
1606
1783
  ;
1607
1784
  /**
@@ -1615,10 +1792,6 @@ function warn() {
1615
1792
  ctx$1.currentTest.warn();
1616
1793
  }
1617
1794
 
1618
- function skipWhen(conditional, callback) {
1619
- ctx.run({ skipped: optionalFunctionValue(conditional) }, function () { return callback(); });
1620
- }
1621
-
1622
1795
  /**
1623
1796
  * Runs a group callback.
1624
1797
  */
@@ -1630,7 +1803,9 @@ function group(groupName, tests) {
1630
1803
  throwGroupError('callback must be a function');
1631
1804
  }
1632
1805
  // Running with the context applied
1633
- ctx.run({ groupName: groupName }, tests);
1806
+ isolate({ type: IsolateTypes.GROUP }, function () {
1807
+ ctx.run({ groupName: groupName }, tests);
1808
+ });
1634
1809
  }
1635
1810
  function throwGroupError(error) {
1636
1811
  throwError("Wrong arguments passed to group. Group " + error + ".");
@@ -1655,29 +1830,6 @@ function optional(optionals) {
1655
1830
  });
1656
1831
  }
1657
1832
 
1658
- /**
1659
- * Removes first found element from array
1660
- * WARNING: Mutates array
1661
- */
1662
- function removeElementFromArray(array, element) {
1663
- var index = array.indexOf(element);
1664
- if (index !== -1) {
1665
- array.splice(index, 1);
1666
- }
1667
- return array;
1668
- }
1669
-
1670
- /**
1671
- * Removes test object from suite state
1672
- */
1673
- function removeTestFromState (testObject) {
1674
- var _a = useTestObjects(), setTestObjects = _a[1];
1675
- setTestObjects(function (testObjects) {
1676
- // using asArray to clear the cache.
1677
- return asArray(removeElementFromArray(testObjects, testObject));
1678
- });
1679
- }
1680
-
1681
1833
  function shouldUseErrorAsMessage(message, error) {
1682
1834
  // kind of cheating with this safe guard, but it does the job
1683
1835
  return isUndefined(message) && isStringValue(error);
@@ -1739,11 +1891,17 @@ var VestTest = /** @class */ (function () {
1739
1891
  return this.hasFailures() || this.isCanceled() || this.isPassing();
1740
1892
  };
1741
1893
  VestTest.prototype.skip = function () {
1894
+ if (this.isPending()) {
1895
+ // Without this condition, the test will be marked as skipped even if it is pending.
1896
+ // This means that it will not be counted in "allIncomplete" and its done callbacks
1897
+ // will not be called, or will be called prematurely.
1898
+ return;
1899
+ }
1742
1900
  this.setStatus(STATUS_SKIPPED);
1743
1901
  };
1744
1902
  VestTest.prototype.cancel = function () {
1745
1903
  this.setStatus(STATUS_CANCELED);
1746
- removeTestFromState(this);
1904
+ useRefreshTestObjects();
1747
1905
  };
1748
1906
  VestTest.prototype.omit = function () {
1749
1907
  this.setStatus(STATUS_OMITTED);
@@ -1867,7 +2025,7 @@ function runSyncTest(testObject) {
1867
2025
  * Registers test, if async - adds to pending array
1868
2026
  */
1869
2027
  function registerTest(testObject) {
1870
- var emit = useBus().emit;
2028
+ var bus = useBus();
1871
2029
  // Run test callback.
1872
2030
  // If a promise is returned, set as async and
1873
2031
  // Move to pending list.
@@ -1881,36 +2039,89 @@ function registerTest(testObject) {
1881
2039
  runAsyncTest(testObject);
1882
2040
  }
1883
2041
  else {
1884
- emit(Events.TEST_COMPLETED, testObject);
2042
+ bus.emit(Events.TEST_COMPLETED, testObject);
1885
2043
  }
1886
2044
  }
1887
- catch (_a) {
1888
- throwError("Your test function " + testObject.fieldName + " returned " + JSON.stringify(result) + ". Only \"false\" or a Promise are supported. Return values may cause unexpected behavior.");
2045
+ catch (e) {
2046
+ throwError("Your test function " + testObject.fieldName + " returned a value. Only \"false\" or Promise returns are supported.");
1889
2047
  }
1890
2048
  }
1891
2049
 
1892
- function useTestAtCursor(initialValue) {
1893
- var cursorAt = useCursorAt()[0];
1894
- var prevTestObjects = usePrevTestObjects()[0];
1895
- if (isNotEmpty(prevTestObjects[cursorAt]) &&
1896
- !isSameProfileTest(prevTestObjects[cursorAt], initialValue)) {
1897
- 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.");
2050
+ /**
2051
+ * This module serves as the "collision detection" mechanism for Vest.
2052
+ * It is used to ensure that tests are not called in a different order than
2053
+ * they were called in the previous run.
2054
+ * If they are, it will throw a deferred error unless explicitly allowed.
2055
+ *
2056
+ * For now it seems pretty safe, and it covers most common use cases, but it can
2057
+ * be improved in the future both in terms of performance and scenarios it covers.
2058
+ */
2059
+ // eslint-disable-next-line max-statements, max-lines-per-function
2060
+ function useTestAtCursor(newTestObject) {
2061
+ var _a = useTestObjects(), testObjects = _a[0], setTestObjects = _a[1];
2062
+ var prevTests = testObjects.prev;
2063
+ if (isEmpty(prevTests)) {
2064
+ useSetTestAtCursor(newTestObject);
2065
+ return newTestObject;
2066
+ }
2067
+ var prevTest = useGetTestAtCursor(prevTests);
2068
+ if (shouldPurgePrevTest(prevTest, newTestObject)) {
2069
+ throwTestOrderError(prevTest, newTestObject);
2070
+ // Here we handle just the omission of tests in the middle of the test suite.
2071
+ // We need to also handle a case in which tests are added in between other tests.
2072
+ // At the moment all we can do is just splice the tests out of the array when this happens.
2073
+ // A viable solution would be to use something like React's key prop to identify tests regardless
2074
+ // of their position in the suite. https://reactjs.org/docs/lists-and-keys.html#keys
2075
+ var current = getCurrent(prevTests, usePath());
2076
+ var cursorAt = useCursorAt();
2077
+ current.splice(cursorAt);
2078
+ // We actually don't mind mutating the state directly (as can be seen above). There is no harm in it
2079
+ // since we're only touching the "prev" state. The reason we still use the setter function is
2080
+ // to prevent future headaches if we ever do need to rely on prev-state immutability.
2081
+ setTestObjects(function (_a) {
2082
+ var current = _a.current;
2083
+ return ({
2084
+ prev: prevTests,
2085
+ current: current
2086
+ });
2087
+ });
2088
+ // Need to see if this has any effect at all.
2089
+ prevTest = null;
1898
2090
  }
1899
- var nextTest = defaultTo(prevTestObjects[cursorAt], initialValue);
2091
+ var nextTest = defaultTo(prevTest, newTestObject);
1900
2092
  useSetTestAtCursor(nextTest);
1901
2093
  return nextTest;
1902
2094
  }
2095
+ function useSetTestAtCursor(testObject) {
2096
+ var cursorPath = usePath();
2097
+ useSetTests(function (tests) {
2098
+ return setValueAtPath(tests, cursorPath, testObject);
2099
+ });
2100
+ }
2101
+ function useGetTestAtCursor(tests) {
2102
+ var cursorPath = usePath();
2103
+ return valueAtPath(tests, cursorPath);
2104
+ }
2105
+ function shouldPurgePrevTest(prevTest, newTest) {
2106
+ return isNotEmpty(prevTest) && !isSameProfileTest(prevTest, newTest);
2107
+ }
2108
+ function throwTestOrderError(prevTest, newTestObject) {
2109
+ if (shouldAllowReorder()) {
2110
+ return;
2111
+ }
2112
+ 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.");
2113
+ }
1903
2114
 
1904
2115
  function registerPrevRunTest(testObject) {
1905
2116
  var prevRunTest = useTestAtCursor(testObject);
1906
2117
  if (isExcluded(testObject)) {
1907
2118
  testObject.skip();
1908
- useSetNextCursorAt();
2119
+ moveForward();
1909
2120
  return prevRunTest;
1910
2121
  }
1911
2122
  cancelOverriddenPendingTest(prevRunTest, testObject);
1912
2123
  useSetTestAtCursor(testObject);
1913
- useSetNextCursorAt();
2124
+ moveForward();
1914
2125
  registerTestObjectByTier(testObject);
1915
2126
  return testObject;
1916
2127
  }
@@ -1929,7 +2140,7 @@ function bindTestEach(test) {
1929
2140
  * Run multiple tests using a parameter table
1930
2141
  */
1931
2142
  function each(table) {
1932
- if (!Array.isArray(table)) {
2143
+ if (!isArray(table)) {
1933
2144
  throwError('test.each: Expected table to be an array.');
1934
2145
  }
1935
2146
  function eachReturn(fieldName) {
@@ -1938,9 +2149,12 @@ function bindTestEach(test) {
1938
2149
  args[_i - 1] = arguments[_i];
1939
2150
  }
1940
2151
  var _a = args.reverse(), testFn = _a[0], message = _a[1];
1941
- return table.map(function (item) {
1942
- item = asArray(item);
1943
- 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); });
2152
+ return isolate({ type: IsolateTypes.EACH }, function () {
2153
+ return table.map(function (item) {
2154
+ item = asArray(item);
2155
+ 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
2156
+ );
2157
+ });
1944
2158
  });
1945
2159
  }
1946
2160
  return eachReturn;
@@ -1958,7 +2172,7 @@ function bindTestMemo(test) {
1958
2172
  args[_i - 1] = arguments[_i];
1959
2173
  }
1960
2174
  var suiteId = useSuiteId()[0];
1961
- var cursorAt = useCursorAt()[0];
2175
+ var cursorAt = useCursorAt();
1962
2176
  var _a = args.reverse(), deps = _a[0], testFn = _a[1], msg = _a[2];
1963
2177
  // Implicit dependency for more specificity
1964
2178
  var dependencies = [suiteId, fieldName, cursorAt].concat(deps);
@@ -1995,6 +2209,6 @@ var test = assign(testBase, {
1995
2209
  memo: bindTestMemo(testBase)
1996
2210
  });
1997
2211
 
1998
- var VERSION = "4.0.0-dev-cc5cf5";
2212
+ var VERSION = "4.0.0-dev-31f012";
1999
2213
 
2000
2214
  export { VERSION, create, enforce, group, only, optional, skip, skipWhen, test, warn };