@vitest/runner 3.2.0-beta.1 → 3.2.0-beta.3

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.
@@ -38,9 +38,11 @@ function interpretTaskModes(file, namePattern, testLocations, onlyMode, parentIs
38
38
  const traverseSuite = (suite, parentIsOnly, parentMatchedWithLocation) => {
39
39
  const suiteIsOnly = parentIsOnly || suite.mode === "only";
40
40
  suite.tasks.forEach((t) => {
41
+ // Check if either the parent suite or the task itself are marked as included
41
42
  const includeTask = suiteIsOnly || t.mode === "only";
42
43
  if (onlyMode) {
43
44
  if (t.type === "suite" && (includeTask || someTasksAreOnly(t))) {
45
+ // Don't skip this suite
44
46
  if (t.mode === "only") {
45
47
  checkAllowOnly(t, allowOnly);
46
48
  t.mode = "run";
@@ -53,6 +55,9 @@ function interpretTaskModes(file, namePattern, testLocations, onlyMode, parentIs
53
55
  }
54
56
  }
55
57
  let hasLocationMatch = parentMatchedWithLocation;
58
+ // Match test location against provided locations, only run if present
59
+ // in `testLocations`. Note: if `includeTaskLocations` is not enabled,
60
+ // all test will be skipped.
56
61
  if (testLocations !== undefined && testLocations.length !== 0) {
57
62
  if (t.location && (testLocations === null || testLocations === void 0 ? void 0 : testLocations.includes(t.location.line))) {
58
63
  t.mode = "run";
@@ -78,6 +83,7 @@ function interpretTaskModes(file, namePattern, testLocations, onlyMode, parentIs
78
83
  }
79
84
  }
80
85
  });
86
+ // if all subtasks are skipped, mark as skip
81
87
  if (suite.mode === "run" || suite.mode === "queued") {
82
88
  if (suite.tasks.length && suite.tasks.every((i) => i.mode !== "run" && i.mode !== "queued")) {
83
89
  suite.mode = "skip";
@@ -186,27 +192,45 @@ function generateFileHash(file, projectName) {
186
192
  * Return a function for running multiple async operations with limited concurrency.
187
193
  */
188
194
  function limitConcurrency(concurrency = Infinity) {
195
+ // The number of currently active + pending tasks.
189
196
  let count = 0;
197
+ // The head and tail of the pending task queue, built using a singly linked list.
198
+ // Both head and tail are initially undefined, signifying an empty queue.
199
+ // They both become undefined again whenever there are no pending tasks.
190
200
  let head;
191
201
  let tail;
202
+ // A bookkeeping function executed whenever a task has been run to completion.
192
203
  const finish = () => {
193
204
  count--;
205
+ // Check if there are further pending tasks in the queue.
194
206
  if (head) {
207
+ // Allow the next pending task to run and pop it from the queue.
195
208
  head[0]();
196
209
  head = head[1];
210
+ // The head may now be undefined if there are no further pending tasks.
211
+ // In that case, set tail to undefined as well.
197
212
  tail = head && tail;
198
213
  }
199
214
  };
200
215
  return (func, ...args) => {
216
+ // Create a promise chain that:
217
+ // 1. Waits for its turn in the task queue (if necessary).
218
+ // 2. Runs the task.
219
+ // 3. Allows the next pending task (if any) to run.
201
220
  return new Promise((resolve) => {
202
221
  if (count++ < concurrency) {
222
+ // No need to queue if fewer than maxConcurrency tasks are running.
203
223
  resolve();
204
224
  } else if (tail) {
225
+ // There are pending tasks, so append to the queue.
205
226
  tail = tail[1] = [resolve];
206
227
  } else {
228
+ // No other pending tasks, initialize the queue with a new tail and head.
207
229
  head = tail = [resolve];
208
230
  }
209
231
  }).then(() => {
232
+ // Running func here ensures that even a non-thenable result or an
233
+ // immediately thrown error gets wrapped into a Promise.
210
234
  return func(...args);
211
235
  }).finally(finish);
212
236
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AfterAllListener, b as AfterEachListener, B as BeforeAllListener, d as BeforeEachListener, e as TaskHook, O as OnTestFailedHandler, f as OnTestFinishedHandler, a as Test, g as Custom, S as Suite, h as SuiteHooks, F as File, i as TaskUpdateEvent, T as Task, j as TestAPI, k as SuiteAPI, l as SuiteCollector } from './tasks.d-hzpC0pGR.js';
2
- export { D as DoneCallback, E as ExtendedContext, m as Fixture, n as FixtureFn, o as FixtureOptions, p as Fixtures, H as HookCleanupCallback, q as HookListener, I as InferFixturesTypes, R as RunMode, r as RuntimeContext, s as SequenceHooks, t as SequenceSetupFiles, u as SuiteFactory, v as TaskBase, w as TaskContext, x as TaskCustomOptions, y as TaskEventPack, z as TaskMeta, G as TaskPopulated, J as TaskResult, K as TaskResultPack, L as TaskState, M as TestContext, N as TestFunction, P as TestOptions, U as Use } from './tasks.d-hzpC0pGR.js';
1
+ import { A as AfterAllListener, b as AfterEachListener, B as BeforeAllListener, d as BeforeEachListener, e as TaskHook, O as OnTestFailedHandler, f as OnTestFinishedHandler, a as Test, g as Custom, S as Suite, h as SuiteHooks, F as File, i as TaskUpdateEvent, T as Task, j as TestAPI, k as SuiteAPI, l as SuiteCollector } from './tasks.d-CItyFU3G.js';
2
+ export { D as DoneCallback, E as ExtendedContext, m as Fixture, n as FixtureFn, o as FixtureOptions, p as Fixtures, H as HookCleanupCallback, q as HookListener, I as InferFixturesTypes, R as RunMode, r as RuntimeContext, s as SequenceHooks, t as SequenceSetupFiles, u as SuiteFactory, v as TaskBase, w as TaskContext, x as TaskCustomOptions, y as TaskEventPack, z as TaskMeta, G as TaskPopulated, J as TaskResult, K as TaskResultPack, L as TaskState, M as TestContext, N as TestFunction, P as TestOptions, U as Use } from './tasks.d-CItyFU3G.js';
3
3
  import { Awaitable } from '@vitest/utils';
4
4
  import { FileSpecification, VitestRunner } from './types.js';
5
5
  export { CancelReason, VitestRunnerConfig, VitestRunnerConstructor, VitestRunnerImportSource } from './types.js';
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ class TestRunAbortError extends Error {
24
24
  }
25
25
  }
26
26
 
27
+ // use WeakMap here to make the Test and Suite object serializable
27
28
  const fnMap = new WeakMap();
28
29
  const testFixtureMap = new WeakMap();
29
30
  const hooksMap = new WeakMap();
@@ -59,6 +60,8 @@ function mergeScopedFixtures(testFixtures, scopedFixtures) {
59
60
  for (const fixtureKep in newFixtures) {
60
61
  var _fixture$deps;
61
62
  const fixture = newFixtures[fixtureKep];
63
+ // if the fixture was define before the scope, then its dep
64
+ // will reference the original fixture instead of the scope
62
65
  fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep) => newFixtures[dep.prop]);
63
66
  }
64
67
  return Object.values(newFixtures);
@@ -68,6 +71,7 @@ function mergeContextFixtures(fixtures, context, inject) {
68
71
  const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
69
72
  const fixtureItem = { value };
70
73
  if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
74
+ // fixture with options
71
75
  Object.assign(fixtureItem, value[1]);
72
76
  const userValue = value[0];
73
77
  fixtureItem.value = fixtureItem.injected ? inject(prop) ?? userValue : userValue;
@@ -81,6 +85,7 @@ function mergeContextFixtures(fixtures, context, inject) {
81
85
  } else {
82
86
  context.fixtures = fixtureArray;
83
87
  }
88
+ // Update dependencies of fixture functions
84
89
  fixtureArray.forEach((fixture) => {
85
90
  if (fixture.isFn) {
86
91
  const usedProps = getUsedProps(fixture.value);
@@ -130,6 +135,7 @@ function withFixtures(fn, testContext) {
130
135
  }
131
136
  async function resolveFixtures() {
132
137
  for (const fixture of pendingFixtures) {
138
+ // fixture could be already initialized during "before" hook
133
139
  if (fixtureValueMap.has(fixture)) {
134
140
  continue;
135
141
  }
@@ -145,22 +151,29 @@ function withFixtures(fn, testContext) {
145
151
  };
146
152
  }
147
153
  async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
154
+ // wait for `use` call to extract fixture value
148
155
  const useFnArgPromise = createDefer();
149
156
  let isUseFnArgResolved = false;
150
157
  const fixtureReturn = fixtureFn(context, async (useFnArg) => {
158
+ // extract `use` argument
151
159
  isUseFnArgResolved = true;
152
160
  useFnArgPromise.resolve(useFnArg);
161
+ // suspend fixture teardown by holding off `useReturnPromise` resolution until cleanup
153
162
  const useReturnPromise = createDefer();
154
163
  cleanupFnArray.push(async () => {
164
+ // start teardown by resolving `use` Promise
155
165
  useReturnPromise.resolve();
166
+ // wait for finishing teardown
156
167
  await fixtureReturn;
157
168
  });
158
169
  await useReturnPromise;
159
170
  }).catch((e) => {
171
+ // treat fixture setup error as test failure
160
172
  if (!isUseFnArgResolved) {
161
173
  useFnArgPromise.reject(e);
162
174
  return;
163
175
  }
176
+ // otherwise re-throw to avoid silencing error during cleanup
164
177
  throw e;
165
178
  });
166
179
  return useFnArgPromise;
@@ -186,6 +199,11 @@ function resolveDeps(fixtures, depSet = new Set(), pendingFixtures = []) {
186
199
  }
187
200
  function getUsedProps(fn) {
188
201
  let fnString = fn.toString();
202
+ // match lowered async function and strip it off
203
+ // example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9Cn0
204
+ // __async(this, null, function*
205
+ // __async(this, arguments, function*
206
+ // __async(this, [_0, _1], function*
189
207
  if (/__async\((?:this|null), (?:null|arguments|\[[_0-9, ]*\]), function\*/.test(fnString)) {
190
208
  fnString = fnString.split(/__async\((?:this|null),/)[1];
191
209
  }
@@ -408,6 +426,7 @@ function getRunner() {
408
426
  function createDefaultSuite(runner) {
409
427
  const config = runner.config.sequence;
410
428
  const collector = suite("", { concurrent: config.concurrent }, () => {});
429
+ // no parent suite for top-level tests
411
430
  delete collector.suite;
412
431
  return collector;
413
432
  }
@@ -437,7 +456,9 @@ function createSuiteHooks() {
437
456
  function parseArguments(optionsOrFn, optionsOrTest) {
438
457
  let options = {};
439
458
  let fn = () => {};
459
+ // it('', () => {}, { retry: 2 })
440
460
  if (typeof optionsOrTest === "object") {
461
+ // it('', { retry: 2 }, { retry: 3 })
441
462
  if (typeof optionsOrFn === "object") {
442
463
  throw new TypeError("Cannot use two objects as arguments. Please provide options and a function callback in that order.");
443
464
  }
@@ -461,6 +482,7 @@ function parseArguments(optionsOrFn, optionsOrTest) {
461
482
  handler: fn
462
483
  };
463
484
  }
485
+ // implementations
464
486
  function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions, parentCollectorFixtures) {
465
487
  const tasks = [];
466
488
  let suite;
@@ -489,11 +511,13 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
489
511
  }
490
512
  task.shuffle = suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle;
491
513
  const context = createTestContext(task, runner);
514
+ // create test context
492
515
  Object.defineProperty(task, "context", {
493
516
  value: context,
494
517
  enumerable: false
495
518
  });
496
519
  setTestFixture(context, options.fixtures);
520
+ // custom can be called from any place, let's assume the limit is 15 stacks
497
521
  const limit = Error.stackTraceLimit;
498
522
  Error.stackTraceLimit = 15;
499
523
  const stackTraceError = new Error("STACK_TRACE_ERROR");
@@ -513,9 +537,11 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
513
537
  };
514
538
  const test = createTest(function(name, optionsOrFn, optionsOrTest) {
515
539
  let { options, handler } = parseArguments(optionsOrFn, optionsOrTest);
540
+ // inherit repeats, retry, timeout from suite
516
541
  if (typeof suiteOptions === "object") {
517
542
  options = Object.assign({}, suiteOptions, options);
518
543
  }
544
+ // inherit concurrent / sequential from suite
519
545
  options.concurrent = this.concurrent || !this.sequential && (options === null || options === void 0 ? void 0 : options.concurrent);
520
546
  options.sequential = this.sequential || !this.concurrent && (options === null || options === void 0 ? void 0 : options.sequential);
521
547
  const test = task(formatName(name), {
@@ -612,6 +638,7 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
612
638
  function withAwaitAsyncAssertions(fn, task) {
613
639
  return async (...args) => {
614
640
  const fnResult = await fn(...args);
641
+ // some async expect will be added to this array, in case user forget to await them
615
642
  if (task.promises) {
616
643
  const result = await Promise.allSettled(task.promises);
617
644
  const errors = result.map((r) => r.status === "rejected" ? r.reason : undefined).filter(Boolean);
@@ -630,11 +657,13 @@ function createSuite() {
630
657
  let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
631
658
  const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
632
659
  const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
660
+ // inherit options from current suite
633
661
  options = {
634
662
  ...currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.options,
635
663
  ...options,
636
664
  shuffle: this.shuffle ?? options.shuffle ?? (currentSuite === null || currentSuite === void 0 || (_currentSuite$options = currentSuite.options) === null || _currentSuite$options === void 0 ? void 0 : _currentSuite$options.shuffle) ?? (runner === null || runner === void 0 ? void 0 : runner.config.sequence.shuffle)
637
665
  };
666
+ // inherit concurrent / sequential from suite
638
667
  const isConcurrent = isConcurrentSpecified || options.concurrent && !isSequentialSpecified;
639
668
  const isSequential = isSequentialSpecified || options.sequential && !isConcurrentSpecified;
640
669
  options.concurrent = isConcurrent && !isSequential;
@@ -735,6 +764,7 @@ function createTaskCollector(fn, context) {
735
764
  const _name = formatName(name);
736
765
  const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
737
766
  cases.forEach((item, idx) => {
767
+ // monkey-patch handler to allow parsing fixture
738
768
  const handlerWrapper = (ctx) => handler(item, ctx);
739
769
  handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
740
770
  handlerWrapper.toString = () => handler.toString();
@@ -788,6 +818,7 @@ function formatName(name) {
788
818
  }
789
819
  function formatTitle(template, items, idx) {
790
820
  if (template.includes("%#") || template.includes("%$")) {
821
+ // '%#' match index of the test case
791
822
  template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/%\$/g, `${idx + 1}`).replace(/__vitest_escaped_%__/g, "%%");
792
823
  }
793
824
  const count = template.split("%").length - 1;
@@ -795,6 +826,7 @@ function formatTitle(template, items, idx) {
795
826
  const placeholders = template.match(/%f/g) || [];
796
827
  placeholders.forEach((_, i) => {
797
828
  if (isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
829
+ // Replace the i-th occurrence of '%f' with '-%f'
798
830
  let occurrence = 0;
799
831
  template = template.replace(/%f/g, (match) => {
800
832
  occurrence++;
@@ -830,6 +862,7 @@ function formatTemplateString(cases, args) {
830
862
  return res;
831
863
  }
832
864
  function findTestFileStackTrace(error, each) {
865
+ // first line is the error message
833
866
  const lines = error.split("\n").slice(1);
834
867
  for (const line of lines) {
835
868
  const stack = parseSingleStack(line);
@@ -862,6 +895,7 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
862
895
  return fn;
863
896
  }
864
897
  const { setTimeout, clearTimeout } = getSafeTimers();
898
+ // this function name is used to filter error in test/cli/test/fails.test.ts
865
899
  return function runWithTimeout(...args) {
866
900
  const startTime = now$2();
867
901
  const runner = getRunner();
@@ -873,6 +907,7 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
873
907
  clearTimeout(timer);
874
908
  rejectTimeoutError();
875
909
  }, timeout);
910
+ // `unref` might not exist in browser
876
911
  (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
877
912
  function rejectTimeoutError() {
878
913
  const error = makeTimeoutError(isHook, timeout, stackTraceError);
@@ -883,6 +918,9 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
883
918
  runner._currentTaskStartTime = undefined;
884
919
  runner._currentTaskTimeout = undefined;
885
920
  clearTimeout(timer);
921
+ // if test/hook took too long in microtask, setTimeout won't be triggered,
922
+ // but we still need to fail the test, see
923
+ // https://github.com/vitest-dev/vitest/issues/2920
886
924
  if (now$2() - startTime >= timeout) {
887
925
  rejectTimeoutError();
888
926
  return;
@@ -895,14 +933,19 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
895
933
  clearTimeout(timer);
896
934
  reject_(error);
897
935
  }
936
+ // sync test/hook will be caught by try/catch
898
937
  try {
899
938
  const result = fn(...args);
939
+ // the result is a thenable, we don't wrap this in Promise.resolve
940
+ // to avoid creating new promises
900
941
  if (typeof result === "object" && result != null && typeof result.then === "function") {
901
942
  result.then(resolve, reject);
902
943
  } else {
903
944
  resolve(result);
904
945
  }
905
- } catch (error) {
946
+ }
947
+ // user sync test/hook throws an error
948
+ catch (error) {
906
949
  reject(error);
907
950
  }
908
951
  });
@@ -932,6 +975,7 @@ function createTestContext(test, runner) {
932
975
  context.task = test;
933
976
  context.skip = (condition, note) => {
934
977
  if (condition === false) {
978
+ // do nothing
935
979
  return undefined;
936
980
  }
937
981
  test.result ?? (test.result = { state: "skip" });
@@ -1174,6 +1218,7 @@ async function collectTests(specs, runner) {
1174
1218
  file.tasks.push(suite);
1175
1219
  }
1176
1220
  } else {
1221
+ // check that types are exhausted
1177
1222
  c;
1178
1223
  }
1179
1224
  }
@@ -1267,6 +1312,7 @@ async function callTestHooks(runner, test, hooks, sequence) {
1267
1312
  async function callSuiteHook(suite, currentTask, name, runner, args) {
1268
1313
  const sequence = runner.config.sequence.hooks;
1269
1314
  const callbacks = [];
1315
+ // stop at file level
1270
1316
  const parentSuite = "filepath" in suite ? null : suite.suite || suite.file;
1271
1317
  if (name === "beforeEach" && parentSuite) {
1272
1318
  callbacks.push(...await callSuiteHook(parentSuite, currentTask, name, runner, args));
@@ -1309,6 +1355,8 @@ function sendTasksUpdate(runner) {
1309
1355
  const p = (_runner$onTaskUpdate = runner.onTaskUpdate) === null || _runner$onTaskUpdate === void 0 ? void 0 : _runner$onTaskUpdate.call(runner, taskPacks, eventsPacks);
1310
1356
  if (p) {
1311
1357
  pendingTasksUpdates.push(p);
1358
+ // remove successful promise to not grow array indefnitely,
1359
+ // but keep rejections so finishSendTasksUpdate can handle them
1312
1360
  p.then(() => pendingTasksUpdates.splice(pendingTasksUpdates.indexOf(p), 1), () => {});
1313
1361
  }
1314
1362
  eventsPacks.length = 0;
@@ -1329,6 +1377,7 @@ function throttle(fn, ms) {
1329
1377
  }
1330
1378
  };
1331
1379
  }
1380
+ // throttle based on summary reporter's DURATION_UPDATE_INTERVAL_MS
1332
1381
  const sendTasksUpdateThrottled = throttle(sendTasksUpdate, 100);
1333
1382
  function updateTask(event, task, runner) {
1334
1383
  eventsPacks.push([task.id, event]);
@@ -1365,6 +1414,9 @@ async function runTest(test, runner) {
1365
1414
  return;
1366
1415
  }
1367
1416
  if (((_test$result = test.result) === null || _test$result === void 0 ? void 0 : _test$result.state) === "fail") {
1417
+ // should not be possible to get here, I think this is just copy pasted from suite
1418
+ // TODO: maybe someone fails tests in `beforeAll` hooks?
1419
+ // https://github.com/vitest-dev/vitest/pull/7069
1368
1420
  updateTask("test-failed-early", test, runner);
1369
1421
  return;
1370
1422
  }
@@ -1434,6 +1486,7 @@ async function runTest(test, runner) {
1434
1486
  }
1435
1487
  test.onFailed = undefined;
1436
1488
  test.onFinished = undefined;
1489
+ // skipped with new PendingError
1437
1490
  if (((_test$result2 = test.result) === null || _test$result2 === void 0 ? void 0 : _test$result2.pending) || ((_test$result3 = test.result) === null || _test$result3 === void 0 ? void 0 : _test$result3.state) === "skip") {
1438
1491
  var _test$result4;
1439
1492
  test.mode = "skip";
@@ -1452,12 +1505,15 @@ async function runTest(test, runner) {
1452
1505
  break;
1453
1506
  }
1454
1507
  if (retryCount < retry) {
1508
+ // reset state when retry test
1455
1509
  test.result.state = "run";
1456
1510
  test.result.retryCount = (test.result.retryCount ?? 0) + 1;
1457
1511
  }
1512
+ // update retry info
1458
1513
  updateTask("test-retried", test, runner);
1459
1514
  }
1460
1515
  }
1516
+ // if test is marked to be failed, flip the result
1461
1517
  if (test.fails) {
1462
1518
  if (test.result.state === "pass") {
1463
1519
  const error = processError(new Error("Expect test to fail"));
@@ -1507,6 +1563,7 @@ async function runSuite(suite, runner) {
1507
1563
  await ((_runner$onBeforeRunSu = runner.onBeforeRunSuite) === null || _runner$onBeforeRunSu === void 0 ? void 0 : _runner$onBeforeRunSu.call(runner, suite));
1508
1564
  if (((_suite$result = suite.result) === null || _suite$result === void 0 ? void 0 : _suite$result.state) === "fail") {
1509
1565
  markTasksAsSkipped(suite, runner);
1566
+ // failed during collection
1510
1567
  updateTask("suite-failed-early", suite, runner);
1511
1568
  return;
1512
1569
  }
@@ -1542,6 +1599,7 @@ async function runSuite(suite, runner) {
1542
1599
  } else {
1543
1600
  const { sequence } = runner.config;
1544
1601
  if (suite.shuffle) {
1602
+ // run describe block independently from tests
1545
1603
  const suites = tasksGroup.filter((group) => group.type === "suite");
1546
1604
  const tests = tasksGroup.filter((group) => group.type === "test");
1547
1605
  const groups = shuffle([suites, tests], sequence.seed);
@@ -1608,7 +1666,10 @@ async function runFiles(files, runner) {
1608
1666
  async function startTests(specs, runner) {
1609
1667
  var _runner$cancel;
1610
1668
  const cancel = (_runner$cancel = runner.cancel) === null || _runner$cancel === void 0 ? void 0 : _runner$cancel.bind(runner);
1669
+ // Ideally, we need to have an event listener for this, but only have a runner here.
1670
+ // Adding another onCancel felt wrong (maybe it needs to be refactored)
1611
1671
  runner.cancel = (reason) => {
1672
+ // We intentionally create only one error since there is only one test run that can be cancelled
1612
1673
  const error = new TestRunAbortError("The test run was aborted by the user.", reason);
1613
1674
  getRunningTests().forEach((test) => abortContextSignal(test.context, error));
1614
1675
  return cancel === null || cancel === void 0 ? void 0 : cancel(reason);
@@ -222,6 +222,7 @@ type Task = Test | Suite | File;
222
222
  */
223
223
  type DoneCallback = (error?: any) => void;
224
224
  type TestFunction<ExtraContext = object> = (context: TestContext & ExtraContext) => Awaitable<any> | void;
225
+ // jest's ExtractEachCallbackArgs
225
226
  type ExtractEachCallbackArgs<T extends ReadonlyArray<any>> = {
226
227
  1: [T[0]]
227
228
  2: [T[0], T[1]]
@@ -257,7 +258,14 @@ interface TestForFunctionReturn<
257
258
  (name: string | Function, options: TestCollectorOptions, fn: (args: Arg, context: Context) => Awaitable<void>): void;
258
259
  }
259
260
  interface TestForFunction<ExtraContext> {
261
+ // test.for([1, 2, 3])
262
+ // test.for([[1, 2], [3, 4, 5]])
260
263
  <T>(cases: ReadonlyArray<T>): TestForFunctionReturn<T, TestContext & ExtraContext>;
264
+ // test.for`
265
+ // a | b
266
+ // {1} | {2}
267
+ // {3} | {4}
268
+ // `
261
269
  (strings: TemplateStringsArray, ...values: any[]): TestForFunctionReturn<any, TestContext & ExtraContext>;
262
270
  }
263
271
  interface SuiteForFunction {
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DiffOptions } from '@vitest/utils/diff';
2
- import { F as File, T as Task, a as Test, S as Suite, K as TaskResultPack, y as TaskEventPack, M as TestContext, s as SequenceHooks, t as SequenceSetupFiles } from './tasks.d-hzpC0pGR.js';
3
- export { A as AfterAllListener, b as AfterEachListener, B as BeforeAllListener, d as BeforeEachListener, g as Custom, j as CustomAPI, D as DoneCallback, E as ExtendedContext, m as Fixture, n as FixtureFn, o as FixtureOptions, p as Fixtures, H as HookCleanupCallback, q as HookListener, I as InferFixturesTypes, O as OnTestFailedHandler, f as OnTestFinishedHandler, R as RunMode, r as RuntimeContext, k as SuiteAPI, l as SuiteCollector, u as SuiteFactory, h as SuiteHooks, v as TaskBase, w as TaskContext, x as TaskCustomOptions, e as TaskHook, z as TaskMeta, G as TaskPopulated, J as TaskResult, L as TaskState, i as TaskUpdateEvent, j as TestAPI, N as TestFunction, P as TestOptions, U as Use } from './tasks.d-hzpC0pGR.js';
2
+ import { F as File, T as Task, a as Test, S as Suite, K as TaskResultPack, y as TaskEventPack, M as TestContext, s as SequenceHooks, t as SequenceSetupFiles } from './tasks.d-CItyFU3G.js';
3
+ export { A as AfterAllListener, b as AfterEachListener, B as BeforeAllListener, d as BeforeEachListener, g as Custom, j as CustomAPI, D as DoneCallback, E as ExtendedContext, m as Fixture, n as FixtureFn, o as FixtureOptions, p as Fixtures, H as HookCleanupCallback, q as HookListener, I as InferFixturesTypes, O as OnTestFailedHandler, f as OnTestFinishedHandler, R as RunMode, r as RuntimeContext, k as SuiteAPI, l as SuiteCollector, u as SuiteFactory, h as SuiteHooks, v as TaskBase, w as TaskContext, x as TaskCustomOptions, e as TaskHook, z as TaskMeta, G as TaskPopulated, J as TaskResult, L as TaskState, i as TaskUpdateEvent, j as TestAPI, N as TestFunction, P as TestOptions, U as Use } from './tasks.d-CItyFU3G.js';
4
4
  import '@vitest/utils';
5
5
 
6
6
  /**
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Suite, F as File, T as Task, a as Test } from './tasks.d-hzpC0pGR.js';
2
- export { C as ChainableFunction, c as createChainable } from './tasks.d-hzpC0pGR.js';
1
+ import { S as Suite, F as File, T as Task, a as Test } from './tasks.d-CItyFU3G.js';
2
+ export { C as ChainableFunction, c as createChainable } from './tasks.d-CItyFU3G.js';
3
3
  import { Arrayable } from '@vitest/utils';
4
4
 
5
5
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/runner",
3
3
  "type": "module",
4
- "version": "3.2.0-beta.1",
4
+ "version": "3.2.0-beta.3",
5
5
  "description": "Vitest test runner",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -39,7 +39,7 @@
39
39
  ],
40
40
  "dependencies": {
41
41
  "pathe": "^2.0.3",
42
- "@vitest/utils": "3.2.0-beta.1"
42
+ "@vitest/utils": "3.2.0-beta.3"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "rimraf dist && rollup -c",