@vitest/runner 2.0.0-beta.1 → 2.0.0-beta.11

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.
@@ -1,4 +1,5 @@
1
1
  import { processError } from '@vitest/utils/error';
2
+ import { relative } from 'pathe';
2
3
  import { toArray } from '@vitest/utils';
3
4
 
4
5
  function partitionSuiteChildren(suite) {
@@ -12,8 +13,9 @@ function partitionSuiteChildren(suite) {
12
13
  tasksGroup = [c];
13
14
  }
14
15
  }
15
- if (tasksGroup.length > 0)
16
+ if (tasksGroup.length > 0) {
16
17
  tasksGroups.push(tasksGroup);
18
+ }
17
19
  return tasksGroups;
18
20
  }
19
21
 
@@ -35,39 +37,50 @@ function interpretTaskModes(suite, namePattern, onlyMode, parentIsOnly, allowOnl
35
37
  }
36
38
  }
37
39
  if (t.type === "test") {
38
- if (namePattern && !getTaskFullName(t).match(namePattern))
40
+ if (namePattern && !getTaskFullName(t).match(namePattern)) {
39
41
  t.mode = "skip";
42
+ }
40
43
  } else if (t.type === "suite") {
41
- if (t.mode === "skip")
44
+ if (t.mode === "skip") {
42
45
  skipAllTasks(t);
43
- else
46
+ } else {
44
47
  interpretTaskModes(t, namePattern, onlyMode, includeTask, allowOnly);
48
+ }
45
49
  }
46
50
  });
47
51
  if (suite.mode === "run") {
48
- if (suite.tasks.length && suite.tasks.every((i) => i.mode !== "run"))
52
+ if (suite.tasks.length && suite.tasks.every((i) => i.mode !== "run")) {
49
53
  suite.mode = "skip";
54
+ }
50
55
  }
51
56
  }
52
57
  function getTaskFullName(task) {
53
58
  return `${task.suite ? `${getTaskFullName(task.suite)} ` : ""}${task.name}`;
54
59
  }
55
60
  function someTasksAreOnly(suite) {
56
- return suite.tasks.some((t) => t.mode === "only" || t.type === "suite" && someTasksAreOnly(t));
61
+ return suite.tasks.some(
62
+ (t) => t.mode === "only" || t.type === "suite" && someTasksAreOnly(t)
63
+ );
57
64
  }
58
65
  function skipAllTasks(suite) {
59
66
  suite.tasks.forEach((t) => {
60
67
  if (t.mode === "run") {
61
68
  t.mode = "skip";
62
- if (t.type === "suite")
69
+ if (t.type === "suite") {
63
70
  skipAllTasks(t);
71
+ }
64
72
  }
65
73
  });
66
74
  }
67
75
  function checkAllowOnly(task, allowOnly) {
68
- if (allowOnly)
76
+ if (allowOnly) {
69
77
  return;
70
- const error = processError(new Error("[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error"));
78
+ }
79
+ const error = processError(
80
+ new Error(
81
+ "[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error"
82
+ )
83
+ );
71
84
  task.result = {
72
85
  state: "fail",
73
86
  errors: [error]
@@ -75,8 +88,9 @@ function checkAllowOnly(task, allowOnly) {
75
88
  }
76
89
  function generateHash(str) {
77
90
  let hash = 0;
78
- if (str.length === 0)
91
+ if (str.length === 0) {
79
92
  return `${hash}`;
93
+ }
80
94
  for (let i = 0; i < str.length; i++) {
81
95
  const char = str.charCodeAt(i);
82
96
  hash = (hash << 5) - hash + char;
@@ -87,10 +101,27 @@ function generateHash(str) {
87
101
  function calculateSuiteHash(parent) {
88
102
  parent.tasks.forEach((t, idx) => {
89
103
  t.id = `${parent.id}_${idx}`;
90
- if (t.type === "suite")
104
+ if (t.type === "suite") {
91
105
  calculateSuiteHash(t);
106
+ }
92
107
  });
93
108
  }
109
+ function createFileTask(filepath, root, projectName) {
110
+ const path = relative(root, filepath);
111
+ const file = {
112
+ id: generateHash(`${path}${projectName || ""}`),
113
+ name: path,
114
+ type: "suite",
115
+ mode: "run",
116
+ filepath,
117
+ tasks: [],
118
+ meta: /* @__PURE__ */ Object.create(null),
119
+ projectName,
120
+ file: void 0
121
+ };
122
+ file.file = file;
123
+ return file;
124
+ }
94
125
 
95
126
  function createChainable(keys, fn) {
96
127
  function create(context) {
@@ -134,8 +165,9 @@ function getTests(suite) {
134
165
  tests.push(task);
135
166
  } else {
136
167
  const taskTests = getTests(task);
137
- for (const test of taskTests)
168
+ for (const test of taskTests) {
138
169
  tests.push(test);
170
+ }
139
171
  }
140
172
  }
141
173
  }
@@ -143,31 +175,41 @@ function getTests(suite) {
143
175
  return tests;
144
176
  }
145
177
  function getTasks(tasks = []) {
146
- return toArray(tasks).flatMap((s) => isAtomTest(s) ? [s] : [s, ...getTasks(s.tasks)]);
178
+ return toArray(tasks).flatMap(
179
+ (s) => isAtomTest(s) ? [s] : [s, ...getTasks(s.tasks)]
180
+ );
147
181
  }
148
182
  function getSuites(suite) {
149
- return toArray(suite).flatMap((s) => s.type === "suite" ? [s, ...getSuites(s.tasks)] : []);
183
+ return toArray(suite).flatMap(
184
+ (s) => s.type === "suite" ? [s, ...getSuites(s.tasks)] : []
185
+ );
150
186
  }
151
187
  function hasTests(suite) {
152
- return toArray(suite).some((s) => s.tasks.some((c) => isAtomTest(c) || hasTests(c)));
188
+ return toArray(suite).some(
189
+ (s) => s.tasks.some((c) => isAtomTest(c) || hasTests(c))
190
+ );
153
191
  }
154
192
  function hasFailed(suite) {
155
- return toArray(suite).some((s) => {
156
- var _a;
157
- return ((_a = s.result) == null ? void 0 : _a.state) === "fail" || s.type === "suite" && hasFailed(s.tasks);
158
- });
193
+ return toArray(suite).some(
194
+ (s) => {
195
+ var _a;
196
+ return ((_a = s.result) == null ? void 0 : _a.state) === "fail" || s.type === "suite" && hasFailed(s.tasks);
197
+ }
198
+ );
159
199
  }
160
200
  function getNames(task) {
161
201
  const names = [task.name];
162
202
  let current = task;
163
203
  while (current == null ? void 0 : current.suite) {
164
204
  current = current.suite;
165
- if (current == null ? void 0 : current.name)
205
+ if (current == null ? void 0 : current.name) {
166
206
  names.unshift(current.name);
207
+ }
167
208
  }
168
- if (current !== task.file)
209
+ if (current !== task.file) {
169
210
  names.unshift(task.file.name);
211
+ }
170
212
  return names;
171
213
  }
172
214
 
173
- export { getTests as a, getTasks as b, calculateSuiteHash as c, getSuites as d, hasFailed as e, getNames as f, generateHash as g, hasTests as h, interpretTaskModes as i, createChainable as j, partitionSuiteChildren as p, someTasksAreOnly as s };
215
+ export { createFileTask as a, isAtomTest as b, calculateSuiteHash as c, getTests as d, getTasks as e, getSuites as f, generateHash as g, hasTests as h, interpretTaskModes as i, hasFailed as j, getNames as k, createChainable as l, partitionSuiteChildren as p, someTasksAreOnly as s };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { VitestRunner } from './types.js';
2
2
  export { CancelReason, VitestRunnerConfig, VitestRunnerConstructor, VitestRunnerImportSource } from './types.js';
3
- import { T as Task, F as File, d as SuiteAPI, e as TestAPI, f as SuiteCollector, g as CustomAPI, h as SuiteHooks, O as OnTestFailedHandler, i as OnTestFinishedHandler, a as Test, C as Custom, S as Suite } from './tasks-Ck0GpLiZ.js';
4
- export { D as DoneCallback, E as ExtendedContext, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, R as RunMode, y as RuntimeContext, B as SequenceHooks, G as SequenceSetupFiles, x as SuiteFactory, k as TaskBase, A as TaskContext, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, o as TaskResultPack, j as TaskState, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-Ck0GpLiZ.js';
3
+ import { T as Task, F as File, d as SuiteAPI, e as TestAPI, f as SuiteCollector, g as CustomAPI, h as SuiteHooks, O as OnTestFailedHandler, i as OnTestFinishedHandler, a as Test, C as Custom, S as Suite } from './tasks-BP89OzIP.js';
4
+ export { D as DoneCallback, E as ExtendedContext, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, R as RunMode, y as RuntimeContext, B as SequenceHooks, G as SequenceSetupFiles, x as SuiteFactory, k as TaskBase, A as TaskContext, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, o as TaskResultPack, j as TaskState, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-BP89OzIP.js';
5
5
  import { Awaitable } from '@vitest/utils';
6
6
  export { processError } from '@vitest/utils/error';
7
7
  import '@vitest/utils/diff';
@@ -23,8 +23,8 @@ declare function afterEach<ExtraContext = {}>(fn: SuiteHooks<ExtraContext>['afte
23
23
  declare const onTestFailed: (fn: OnTestFailedHandler) => void;
24
24
  declare const onTestFinished: (fn: OnTestFinishedHandler) => void;
25
25
 
26
- declare function setFn(key: Test | Custom, fn: (() => Awaitable<void>)): void;
27
- declare function getFn<Task = Test | Custom>(key: Task): (() => Awaitable<void>);
26
+ declare function setFn(key: Test | Custom, fn: () => Awaitable<void>): void;
27
+ declare function getFn<Task = Test | Custom>(key: Task): () => Awaitable<void>;
28
28
  declare function setHooks(key: Suite, hooks: SuiteHooks): void;
29
29
  declare function getHooks(key: Suite): SuiteHooks;
30
30
 
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import limit from 'p-limit';
2
- import { getSafeTimers, isObject, createDefer, format, objDisplay, objectAttr, toArray, shuffle } from '@vitest/utils';
2
+ import { getSafeTimers, isObject, createDefer, isNegativeNaN, format, objDisplay, objectAttr, toArray, shuffle } from '@vitest/utils';
3
3
  import { processError } from '@vitest/utils/error';
4
4
  export { processError } from '@vitest/utils/error';
5
- import { j as createChainable, g as generateHash, c as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, p as partitionSuiteChildren, h as hasTests, e as hasFailed } from './chunk-tasks.js';
6
- import { relative } from 'pathe';
5
+ import { l as createChainable, a as createFileTask, c as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, p as partitionSuiteChildren, h as hasTests, j as hasFailed } from './chunk-tasks.js';
7
6
  import { parseSingleStack } from '@vitest/utils/source-map';
7
+ import 'pathe';
8
8
 
9
9
  const fnMap = /* @__PURE__ */ new WeakMap();
10
10
  const fixtureMap = /* @__PURE__ */ new WeakMap();
@@ -53,18 +53,22 @@ async function runWithSuite(suite, fn) {
53
53
  collectorContext.currentSuite = prev;
54
54
  }
55
55
  function withTimeout(fn, timeout, isHook = false) {
56
- if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY)
56
+ if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) {
57
57
  return fn;
58
+ }
58
59
  const { setTimeout, clearTimeout } = getSafeTimers();
59
60
  return (...args) => {
60
- return Promise.race([fn(...args), new Promise((resolve, reject) => {
61
- var _a;
62
- const timer = setTimeout(() => {
63
- clearTimeout(timer);
64
- reject(new Error(makeTimeoutMsg(isHook, timeout)));
65
- }, timeout);
66
- (_a = timer.unref) == null ? void 0 : _a.call(timer);
67
- })]);
61
+ return Promise.race([
62
+ fn(...args),
63
+ new Promise((resolve, reject) => {
64
+ var _a;
65
+ const timer = setTimeout(() => {
66
+ clearTimeout(timer);
67
+ reject(new Error(makeTimeoutMsg(isHook, timeout)));
68
+ }, timeout);
69
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
70
+ })
71
+ ]);
68
72
  };
69
73
  }
70
74
  function createTestContext(test, runner) {
@@ -94,25 +98,31 @@ If this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as t
94
98
 
95
99
  function mergeContextFixtures(fixtures, context = {}) {
96
100
  const fixtureOptionKeys = ["auto"];
97
- const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
98
- const fixtureItem = { value };
99
- if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
100
- Object.assign(fixtureItem, value[1]);
101
- fixtureItem.value = value[0];
101
+ const fixtureArray = Object.entries(fixtures).map(
102
+ ([prop, value]) => {
103
+ const fixtureItem = { value };
104
+ if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
105
+ Object.assign(fixtureItem, value[1]);
106
+ fixtureItem.value = value[0];
107
+ }
108
+ fixtureItem.prop = prop;
109
+ fixtureItem.isFn = typeof fixtureItem.value === "function";
110
+ return fixtureItem;
102
111
  }
103
- fixtureItem.prop = prop;
104
- fixtureItem.isFn = typeof fixtureItem.value === "function";
105
- return fixtureItem;
106
- });
107
- if (Array.isArray(context.fixtures))
112
+ );
113
+ if (Array.isArray(context.fixtures)) {
108
114
  context.fixtures = context.fixtures.concat(fixtureArray);
109
- else
115
+ } else {
110
116
  context.fixtures = fixtureArray;
117
+ }
111
118
  fixtureArray.forEach((fixture) => {
112
119
  if (fixture.isFn) {
113
120
  const usedProps = getUsedProps(fixture.value);
114
- if (usedProps.length)
115
- fixture.deps = context.fixtures.filter(({ prop }) => prop !== fixture.prop && usedProps.includes(prop));
121
+ if (usedProps.length) {
122
+ fixture.deps = context.fixtures.filter(
123
+ ({ prop }) => prop !== fixture.prop && usedProps.includes(prop)
124
+ );
125
+ }
116
126
  }
117
127
  });
118
128
  return context;
@@ -121,36 +131,46 @@ const fixtureValueMaps = /* @__PURE__ */ new Map();
121
131
  const cleanupFnArrayMap = /* @__PURE__ */ new Map();
122
132
  async function callFixtureCleanup(context) {
123
133
  const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
124
- for (const cleanup of cleanupFnArray.reverse())
134
+ for (const cleanup of cleanupFnArray.reverse()) {
125
135
  await cleanup();
136
+ }
126
137
  cleanupFnArrayMap.delete(context);
127
138
  }
128
139
  function withFixtures(fn, testContext) {
129
140
  return (hookContext) => {
130
141
  const context = hookContext || testContext;
131
- if (!context)
142
+ if (!context) {
132
143
  return fn({});
144
+ }
133
145
  const fixtures = getFixture(context);
134
- if (!(fixtures == null ? void 0 : fixtures.length))
146
+ if (!(fixtures == null ? void 0 : fixtures.length)) {
135
147
  return fn(context);
148
+ }
136
149
  const usedProps = getUsedProps(fn);
137
150
  const hasAutoFixture = fixtures.some(({ auto }) => auto);
138
- if (!usedProps.length && !hasAutoFixture)
151
+ if (!usedProps.length && !hasAutoFixture) {
139
152
  return fn(context);
140
- if (!fixtureValueMaps.get(context))
153
+ }
154
+ if (!fixtureValueMaps.get(context)) {
141
155
  fixtureValueMaps.set(context, /* @__PURE__ */ new Map());
156
+ }
142
157
  const fixtureValueMap = fixtureValueMaps.get(context);
143
- if (!cleanupFnArrayMap.has(context))
158
+ if (!cleanupFnArrayMap.has(context)) {
144
159
  cleanupFnArrayMap.set(context, []);
160
+ }
145
161
  const cleanupFnArray = cleanupFnArrayMap.get(context);
146
- const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop));
162
+ const usedFixtures = fixtures.filter(
163
+ ({ prop, auto }) => auto || usedProps.includes(prop)
164
+ );
147
165
  const pendingFixtures = resolveDeps(usedFixtures);
148
- if (!pendingFixtures.length)
166
+ if (!pendingFixtures.length) {
149
167
  return fn(context);
168
+ }
150
169
  async function resolveFixtures() {
151
170
  for (const fixture of pendingFixtures) {
152
- if (fixtureValueMap.has(fixture))
171
+ if (fixtureValueMap.has(fixture)) {
153
172
  continue;
173
+ }
154
174
  const resolvedValue = fixture.isFn ? await resolveFixtureFunction(fixture.value, context, cleanupFnArray) : fixture.value;
155
175
  context[fixture.prop] = resolvedValue;
156
176
  fixtureValueMap.set(fixture, resolvedValue);
@@ -185,14 +205,18 @@ async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
185
205
  }
186
206
  function resolveDeps(fixtures, depSet = /* @__PURE__ */ new Set(), pendingFixtures = []) {
187
207
  fixtures.forEach((fixture) => {
188
- if (pendingFixtures.includes(fixture))
208
+ if (pendingFixtures.includes(fixture)) {
189
209
  return;
210
+ }
190
211
  if (!fixture.isFn || !fixture.deps) {
191
212
  pendingFixtures.push(fixture);
192
213
  return;
193
214
  }
194
- if (depSet.has(fixture))
195
- throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`);
215
+ if (depSet.has(fixture)) {
216
+ throw new Error(
217
+ `Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`
218
+ );
219
+ }
196
220
  depSet.add(fixture);
197
221
  resolveDeps(fixture.deps, depSet, pendingFixtures);
198
222
  pendingFixtures.push(fixture);
@@ -202,21 +226,35 @@ function resolveDeps(fixtures, depSet = /* @__PURE__ */ new Set(), pendingFixtur
202
226
  }
203
227
  function getUsedProps(fn) {
204
228
  const match = fn.toString().match(/[^(]*\(([^)]*)/);
205
- if (!match)
229
+ if (!match) {
206
230
  return [];
231
+ }
207
232
  const args = splitByComma(match[1]);
208
- if (!args.length)
233
+ if (!args.length) {
209
234
  return [];
210
- const first = args[0];
211
- if (!(first.startsWith("{") && first.endsWith("}")))
212
- throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
235
+ }
236
+ let first = args[0];
237
+ if ("__VITEST_FIXTURE_INDEX__" in fn) {
238
+ first = args[fn.__VITEST_FIXTURE_INDEX__];
239
+ if (!first) {
240
+ return [];
241
+ }
242
+ }
243
+ if (!(first.startsWith("{") && first.endsWith("}"))) {
244
+ throw new Error(
245
+ `The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`
246
+ );
247
+ }
213
248
  const _first = first.slice(1, -1).replace(/\s/g, "");
214
249
  const props = splitByComma(_first).map((prop) => {
215
- return prop.replace(/\:.*|\=.*/g, "");
250
+ return prop.replace(/:.*|=.*/g, "");
216
251
  });
217
252
  const last = props.at(-1);
218
- if (last && last.startsWith("..."))
219
- throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
253
+ if (last && last.startsWith("...")) {
254
+ throw new Error(
255
+ `Rest parameters are not supported in fixtures, received "${last}".`
256
+ );
257
+ }
220
258
  return props;
221
259
  }
222
260
  function splitByComma(s) {
@@ -230,14 +268,16 @@ function splitByComma(s) {
230
268
  stack.pop();
231
269
  } else if (!stack.length && s[i] === ",") {
232
270
  const token = s.substring(start, i).trim();
233
- if (token)
271
+ if (token) {
234
272
  result.push(token);
273
+ }
235
274
  start = i + 1;
236
275
  }
237
276
  }
238
277
  const lastToken = s.substring(start).trim();
239
- if (lastToken)
278
+ if (lastToken) {
240
279
  result.push(lastToken);
280
+ }
241
281
  return result;
242
282
  }
243
283
 
@@ -250,13 +290,19 @@ function getCurrentTest() {
250
290
  }
251
291
 
252
292
  const suite = createSuite();
253
- const test = createTest(
254
- function(name, optionsOrFn, optionsOrTest) {
255
- if (getCurrentTest())
256
- throw new Error('Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.');
257
- getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
293
+ const test = createTest(function(name, optionsOrFn, optionsOrTest) {
294
+ if (getCurrentTest()) {
295
+ throw new Error(
296
+ 'Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.'
297
+ );
258
298
  }
259
- );
299
+ getCurrentSuite().test.fn.call(
300
+ this,
301
+ formatName(name),
302
+ optionsOrFn,
303
+ optionsOrTest
304
+ );
305
+ });
260
306
  const describe = suite;
261
307
  const it = test;
262
308
  let runner;
@@ -278,8 +324,9 @@ function createDefaultSuite(runner2) {
278
324
  });
279
325
  }
280
326
  function clearCollectorContext(filepath, currentRunner) {
281
- if (!defaultSuite)
327
+ if (!defaultSuite) {
282
328
  defaultSuite = createDefaultSuite(currentRunner);
329
+ }
283
330
  runner = currentRunner;
284
331
  currentTestFilepath = filepath;
285
332
  collectorContext.tasks.length = 0;
@@ -302,8 +349,11 @@ function parseArguments(optionsOrFn, optionsOrTest) {
302
349
  let fn = () => {
303
350
  };
304
351
  if (typeof optionsOrTest === "object") {
305
- if (typeof optionsOrFn === "object")
306
- throw new TypeError("Cannot use two objects as arguments. Please provide options and a function callback in that order.");
352
+ if (typeof optionsOrFn === "object") {
353
+ throw new TypeError(
354
+ "Cannot use two objects as arguments. Please provide options and a function callback in that order."
355
+ );
356
+ }
307
357
  options = optionsOrTest;
308
358
  } else if (typeof optionsOrTest === "number") {
309
359
  options = { timeout: optionsOrTest };
@@ -311,8 +361,11 @@ function parseArguments(optionsOrFn, optionsOrTest) {
311
361
  options = optionsOrFn;
312
362
  }
313
363
  if (typeof optionsOrFn === "function") {
314
- if (typeof optionsOrTest === "function")
315
- throw new TypeError("Cannot use two functions as arguments. Please use the second argument for options.");
364
+ if (typeof optionsOrTest === "function") {
365
+ throw new TypeError(
366
+ "Cannot use two functions as arguments. Please use the second argument for options."
367
+ );
368
+ }
316
369
  fn = optionsOrFn;
317
370
  } else if (typeof optionsOrTest === "function") {
318
371
  fn = optionsOrTest;
@@ -344,10 +397,12 @@ function createSuiteCollector(name, factory = () => {
344
397
  meta: options.meta ?? /* @__PURE__ */ Object.create(null)
345
398
  };
346
399
  const handler = options.handler;
347
- if (options.concurrent || !options.sequential && runner.config.sequence.concurrent)
400
+ if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
348
401
  task2.concurrent = true;
349
- if (shuffle)
402
+ }
403
+ if (shuffle) {
350
404
  task2.shuffle = true;
405
+ }
351
406
  const context = createTestContext(task2, runner);
352
407
  Object.defineProperty(task2, "context", {
353
408
  value: context,
@@ -355,10 +410,13 @@ function createSuiteCollector(name, factory = () => {
355
410
  });
356
411
  setFixture(context, options.fixtures);
357
412
  if (handler) {
358
- setFn(task2, withTimeout(
359
- withFixtures(handler, context),
360
- (options == null ? void 0 : options.timeout) ?? runner.config.testTimeout
361
- ));
413
+ setFn(
414
+ task2,
415
+ withTimeout(
416
+ withFixtures(handler, context),
417
+ (options == null ? void 0 : options.timeout) ?? runner.config.testTimeout
418
+ )
419
+ );
362
420
  }
363
421
  if (runner.config.includeTaskLocation) {
364
422
  const limit = Error.stackTraceLimit;
@@ -366,25 +424,25 @@ function createSuiteCollector(name, factory = () => {
366
424
  const error = new Error("stacktrace").stack;
367
425
  Error.stackTraceLimit = limit;
368
426
  const stack = findTestFileStackTrace(error, task2.each ?? false);
369
- if (stack)
427
+ if (stack) {
370
428
  task2.location = stack;
429
+ }
371
430
  }
372
431
  tasks.push(task2);
373
432
  return task2;
374
433
  };
375
434
  const test2 = createTest(function(name2, optionsOrFn, optionsOrTest) {
376
- let { options, handler } = parseArguments(
377
- optionsOrFn,
378
- optionsOrTest
379
- );
380
- if (typeof suiteOptions === "object")
435
+ let { options, handler } = parseArguments(optionsOrFn, optionsOrTest);
436
+ if (typeof suiteOptions === "object") {
381
437
  options = Object.assign({}, suiteOptions, options);
438
+ }
382
439
  options.concurrent = this.concurrent || !this.sequential && (options == null ? void 0 : options.concurrent);
383
440
  options.sequential = this.sequential || !this.concurrent && (options == null ? void 0 : options.sequential);
384
- const test3 = task(
385
- formatName(name2),
386
- { ...this, ...options, handler }
387
- );
441
+ const test3 = task(formatName(name2), {
442
+ ...this,
443
+ ...options,
444
+ handler
445
+ });
388
446
  test3.type = "test";
389
447
  });
390
448
  const collector = {
@@ -403,8 +461,9 @@ function createSuiteCollector(name, factory = () => {
403
461
  getHooks(suite2)[name2].push(...fn);
404
462
  }
405
463
  function initSuite(includeLocation) {
406
- if (typeof suiteOptions === "number")
464
+ if (typeof suiteOptions === "number") {
407
465
  suiteOptions = { timeout: suiteOptions };
466
+ }
408
467
  suite2 = {
409
468
  id: "",
410
469
  type: "suite",
@@ -414,7 +473,8 @@ function createSuiteCollector(name, factory = () => {
414
473
  file: void 0,
415
474
  shuffle,
416
475
  tasks: [],
417
- meta: /* @__PURE__ */ Object.create(null)
476
+ meta: /* @__PURE__ */ Object.create(null),
477
+ concurrent: suiteOptions == null ? void 0 : suiteOptions.concurrent
418
478
  };
419
479
  if (runner && includeLocation && runner.config.includeTaskLocation) {
420
480
  const limit = Error.stackTraceLimit;
@@ -422,8 +482,9 @@ function createSuiteCollector(name, factory = () => {
422
482
  const error = new Error("stacktrace").stack;
423
483
  Error.stackTraceLimit = limit;
424
484
  const stack = findTestFileStackTrace(error, suite2.each ?? false);
425
- if (stack)
485
+ if (stack) {
426
486
  suite2.location = stack;
487
+ }
427
488
  }
428
489
  setHooks(suite2, createSuiteHooks());
429
490
  }
@@ -433,14 +494,17 @@ function createSuiteCollector(name, factory = () => {
433
494
  initSuite(false);
434
495
  }
435
496
  async function collect(file) {
436
- if (!file)
497
+ if (!file) {
437
498
  throw new TypeError("File is required to collect tasks.");
499
+ }
438
500
  factoryQueue.length = 0;
439
- if (factory)
501
+ if (factory) {
440
502
  await runWithSuite(collector, () => factory(test2));
503
+ }
441
504
  const allChildren = [];
442
- for (const i of [...factoryQueue, ...tasks])
505
+ for (const i of [...factoryQueue, ...tasks]) {
443
506
  allChildren.push(i.type === "collector" ? await i.collect(file) : i);
507
+ }
444
508
  suite2.file = file;
445
509
  suite2.tasks = allChildren;
446
510
  allChildren.forEach((task2) => {
@@ -460,29 +524,41 @@ function createSuite() {
460
524
  factoryOrOptions,
461
525
  optionsOrFactory
462
526
  );
463
- if (currentSuite == null ? void 0 : currentSuite.options)
527
+ if (currentSuite == null ? void 0 : currentSuite.options) {
464
528
  options = { ...currentSuite.options, ...options };
465
- options.concurrent = this.concurrent || !this.sequential && (options == null ? void 0 : options.concurrent);
466
- options.sequential = this.sequential || !this.concurrent && (options == null ? void 0 : options.sequential);
467
- return createSuiteCollector(formatName(name), factory, mode, this.shuffle, this.each, options);
529
+ }
530
+ const isConcurrent = options.concurrent || this.concurrent && !this.sequential;
531
+ const isSequential = options.sequential || this.sequential && !this.concurrent;
532
+ options.concurrent = isConcurrent && !isSequential;
533
+ options.sequential = isSequential && !isConcurrent;
534
+ return createSuiteCollector(
535
+ formatName(name),
536
+ factory,
537
+ mode,
538
+ this.shuffle,
539
+ this.each,
540
+ options
541
+ );
468
542
  }
469
543
  suiteFn.each = function(cases, ...args) {
470
544
  const suite2 = this.withContext();
471
545
  this.setContext("each", true);
472
- if (Array.isArray(cases) && args.length)
546
+ if (Array.isArray(cases) && args.length) {
473
547
  cases = formatTemplateString(cases, args);
548
+ }
474
549
  return (name, optionsOrFn, fnOrOptions) => {
475
550
  const _name = formatName(name);
476
551
  const arrayOnlyCases = cases.every(Array.isArray);
477
- const { options, handler } = parseArguments(
478
- optionsOrFn,
479
- fnOrOptions
480
- );
552
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
481
553
  const fnFirst = typeof optionsOrFn === "function";
482
554
  cases.forEach((i, idx) => {
483
555
  const items = Array.isArray(i) ? i : [i];
484
556
  if (fnFirst) {
485
- arrayOnlyCases ? suite2(formatTitle(_name, items, idx), () => handler(...items), options) : suite2(formatTitle(_name, items, idx), () => handler(i), options);
557
+ arrayOnlyCases ? suite2(
558
+ formatTitle(_name, items, idx),
559
+ () => handler(...items),
560
+ options
561
+ ) : suite2(formatTitle(_name, items, idx), () => handler(i), options);
486
562
  } else {
487
563
  arrayOnlyCases ? suite2(formatTitle(_name, items, idx), options, () => handler(...items)) : suite2(formatTitle(_name, items, idx), options, () => handler(i));
488
564
  }
@@ -502,20 +578,22 @@ function createTaskCollector(fn, context) {
502
578
  taskFn.each = function(cases, ...args) {
503
579
  const test2 = this.withContext();
504
580
  this.setContext("each", true);
505
- if (Array.isArray(cases) && args.length)
581
+ if (Array.isArray(cases) && args.length) {
506
582
  cases = formatTemplateString(cases, args);
583
+ }
507
584
  return (name, optionsOrFn, fnOrOptions) => {
508
585
  const _name = formatName(name);
509
586
  const arrayOnlyCases = cases.every(Array.isArray);
510
- const { options, handler } = parseArguments(
511
- optionsOrFn,
512
- fnOrOptions
513
- );
587
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
514
588
  const fnFirst = typeof optionsOrFn === "function";
515
589
  cases.forEach((i, idx) => {
516
590
  const items = Array.isArray(i) ? i : [i];
517
591
  if (fnFirst) {
518
- arrayOnlyCases ? test2(formatTitle(_name, items, idx), () => handler(...items), options) : test2(formatTitle(_name, items, idx), () => handler(i), options);
592
+ arrayOnlyCases ? test2(
593
+ formatTitle(_name, items, idx),
594
+ () => handler(...items),
595
+ options
596
+ ) : test2(formatTitle(_name, items, idx), () => handler(i), options);
519
597
  } else {
520
598
  arrayOnlyCases ? test2(formatTitle(_name, items, idx), options, () => handler(...items)) : test2(formatTitle(_name, items, idx), options, () => handler(i));
521
599
  }
@@ -523,6 +601,22 @@ function createTaskCollector(fn, context) {
523
601
  this.setContext("each", void 0);
524
602
  };
525
603
  };
604
+ taskFn.for = function(cases, ...args) {
605
+ const test2 = this.withContext();
606
+ if (Array.isArray(cases) && args.length) {
607
+ cases = formatTemplateString(cases, args);
608
+ }
609
+ return (name, optionsOrFn, fnOrOptions) => {
610
+ const _name = formatName(name);
611
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
612
+ cases.forEach((item, idx) => {
613
+ const handlerWrapper = (ctx) => handler(item, ctx);
614
+ handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
615
+ handlerWrapper.toString = () => handler.toString();
616
+ test2(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
617
+ });
618
+ };
619
+ };
526
620
  taskFn.skipIf = function(condition) {
527
621
  return condition ? this.skip : this;
528
622
  };
@@ -532,15 +626,21 @@ function createTaskCollector(fn, context) {
532
626
  taskFn.extend = function(fixtures) {
533
627
  const _context = mergeContextFixtures(fixtures, context);
534
628
  return createTest(function fn2(name, optionsOrFn, optionsOrTest) {
535
- getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
629
+ getCurrentSuite().test.fn.call(
630
+ this,
631
+ formatName(name),
632
+ optionsOrFn,
633
+ optionsOrTest
634
+ );
536
635
  }, _context);
537
636
  };
538
637
  const _test = createChainable(
539
638
  ["concurrent", "sequential", "skip", "only", "todo", "fails"],
540
639
  taskFn
541
640
  );
542
- if (context)
641
+ if (context) {
543
642
  _test.mergeContext(context);
643
+ }
544
644
  return _test;
545
645
  }
546
646
  function createTest(fn, context) {
@@ -554,14 +654,28 @@ function formatTitle(template, items, idx) {
554
654
  template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/__vitest_escaped_%__/g, "%%");
555
655
  }
556
656
  const count = template.split("%").length - 1;
657
+ if (template.includes("%f")) {
658
+ const placeholders = template.match(/%f/g) || [];
659
+ placeholders.forEach((_, i) => {
660
+ if (isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
661
+ let occurrence = 0;
662
+ template = template.replace(/%f/g, (match) => {
663
+ occurrence++;
664
+ return occurrence === i + 1 ? "-%f" : match;
665
+ });
666
+ }
667
+ });
668
+ }
557
669
  let formatted = format(template, ...items.slice(0, count));
558
670
  if (isObject(items[0])) {
559
671
  formatted = formatted.replace(
560
- /\$([$\w_.]+)/g,
672
+ /\$([$\w.]+)/g,
561
673
  // https://github.com/chaijs/chai/pull/1490
562
674
  (_, key) => {
563
675
  var _a, _b;
564
- return objDisplay(objectAttr(items[0], key), { truncate: (_b = (_a = runner == null ? void 0 : runner.config) == null ? void 0 : _a.chaiConfig) == null ? void 0 : _b.truncateThreshold });
676
+ return objDisplay(objectAttr(items[0], key), {
677
+ truncate: (_b = (_a = runner == null ? void 0 : runner.config) == null ? void 0 : _a.chaiConfig) == null ? void 0 : _b.truncateThreshold
678
+ });
565
679
  }
566
680
  );
567
681
  }
@@ -572,8 +686,9 @@ function formatTemplateString(cases, args) {
572
686
  const res = [];
573
687
  for (let i = 0; i < Math.floor(args.length / header.length); i++) {
574
688
  const oneCase = {};
575
- for (let j = 0; j < header.length; j++)
689
+ for (let j = 0; j < header.length; j++) {
576
690
  oneCase[header[j]] = args[i * header.length + j];
691
+ }
577
692
  res.push(oneCase);
578
693
  }
579
694
  return res;
@@ -606,29 +721,20 @@ async function runSetupFiles(config, runner) {
606
721
  })
607
722
  );
608
723
  } else {
609
- for (const fsPath of files)
724
+ for (const fsPath of files) {
610
725
  await runner.importFile(fsPath, "setup");
726
+ }
611
727
  }
612
728
  }
613
729
 
614
730
  const now$1 = Date.now;
615
731
  async function collectTests(paths, runner) {
732
+ var _a;
616
733
  const files = [];
617
734
  const config = runner.config;
618
735
  for (const filepath of paths) {
619
- const path = relative(config.root, filepath);
620
- const file = {
621
- id: generateHash(`${path}${config.name || ""}`),
622
- name: path,
623
- type: "suite",
624
- mode: "run",
625
- filepath,
626
- tasks: [],
627
- meta: /* @__PURE__ */ Object.create(null),
628
- projectName: config.name,
629
- file: void 0
630
- };
631
- file.file = file;
736
+ const file = createFileTask(filepath, config.root, config.name);
737
+ (_a = runner.onCollectStart) == null ? void 0 : _a.call(runner, file);
632
738
  clearCollectorContext(filepath, runner);
633
739
  try {
634
740
  const setupStart = now$1();
@@ -663,11 +769,18 @@ async function collectTests(paths, runner) {
663
769
  }
664
770
  calculateSuiteHash(file);
665
771
  const hasOnlyTasks = someTasksAreOnly(file);
666
- interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly);
772
+ interpretTaskModes(
773
+ file,
774
+ config.testNamePattern,
775
+ hasOnlyTasks,
776
+ false,
777
+ config.allowOnly
778
+ );
667
779
  file.tasks.forEach((task) => {
668
- var _a;
669
- if (((_a = task.suite) == null ? void 0 : _a.id) === "")
780
+ var _a2;
781
+ if (((_a2 = task.suite) == null ? void 0 : _a2.id) === "") {
670
782
  delete task.suite;
783
+ }
671
784
  });
672
785
  files.push(file);
673
786
  }
@@ -684,10 +797,12 @@ function mergeHooks(baseHooks, hooks) {
684
797
  const now = Date.now;
685
798
  function updateSuiteHookState(suite, name, state, runner) {
686
799
  var _a;
687
- if (!suite.result)
800
+ if (!suite.result) {
688
801
  suite.result = { state: "run" };
689
- if (!((_a = suite.result) == null ? void 0 : _a.hooks))
802
+ }
803
+ if (!((_a = suite.result) == null ? void 0 : _a.hooks)) {
690
804
  suite.result.hooks = {};
805
+ }
691
806
  const suiteHooks = suite.result.hooks;
692
807
  if (suiteHooks) {
693
808
  suiteHooks[name] = state;
@@ -696,18 +811,21 @@ function updateSuiteHookState(suite, name, state, runner) {
696
811
  }
697
812
  function getSuiteHooks(suite, name, sequence) {
698
813
  const hooks = getHooks(suite)[name];
699
- if (sequence === "stack" && (name === "afterAll" || name === "afterEach"))
814
+ if (sequence === "stack" && (name === "afterAll" || name === "afterEach")) {
700
815
  return hooks.slice().reverse();
816
+ }
701
817
  return hooks;
702
818
  }
703
819
  async function callTaskHooks(task, hooks, sequence) {
704
- if (sequence === "stack")
820
+ if (sequence === "stack") {
705
821
  hooks = hooks.slice().reverse();
822
+ }
706
823
  if (sequence === "parallel") {
707
824
  await Promise.all(hooks.map((fn) => fn(task.result)));
708
825
  } else {
709
- for (const fn of hooks)
826
+ for (const fn of hooks) {
710
827
  await fn(task.result);
828
+ }
711
829
  }
712
830
  }
713
831
  async function callSuiteHook(suite, currentTask, name, runner, args) {
@@ -722,10 +840,13 @@ async function callSuiteHook(suite, currentTask, name, runner, args) {
722
840
  updateSuiteHookState(currentTask, name, "run", runner);
723
841
  const hooks = getSuiteHooks(suite, name, sequence);
724
842
  if (sequence === "parallel") {
725
- callbacks.push(...await Promise.all(hooks.map((fn) => fn(...args))));
843
+ callbacks.push(
844
+ ...await Promise.all(hooks.map((fn) => fn(...args)))
845
+ );
726
846
  } else {
727
- for (const hook of hooks)
847
+ for (const hook of hooks) {
728
848
  callbacks.push(await hook(...args));
849
+ }
729
850
  }
730
851
  updateSuiteHookState(currentTask, name, "pass", runner);
731
852
  if (name === "afterEach" && parentSuite) {
@@ -753,11 +874,7 @@ async function sendTasksUpdate(runner) {
753
874
  await previousUpdate;
754
875
  if (packs.size) {
755
876
  const taskPacks = Array.from(packs).map(([id, task]) => {
756
- return [
757
- id,
758
- task[0],
759
- task[1]
760
- ];
877
+ return [id, task[0], task[1]];
761
878
  });
762
879
  const p = (_a = runner.onTaskUpdate) == null ? void 0 : _a.call(runner, taskPacks);
763
880
  packs.clear();
@@ -765,17 +882,21 @@ async function sendTasksUpdate(runner) {
765
882
  }
766
883
  }
767
884
  async function callCleanupHooks(cleanups) {
768
- await Promise.all(cleanups.map(async (fn) => {
769
- if (typeof fn !== "function")
770
- return;
771
- await fn();
772
- }));
885
+ await Promise.all(
886
+ cleanups.map(async (fn) => {
887
+ if (typeof fn !== "function") {
888
+ return;
889
+ }
890
+ await fn();
891
+ })
892
+ );
773
893
  }
774
894
  async function runTest(test, runner) {
775
895
  var _a, _b, _c, _d, _e, _f;
776
896
  await ((_a = runner.onBeforeRunTask) == null ? void 0 : _a.call(runner, test));
777
- if (test.mode !== "run")
897
+ if (test.mode !== "run") {
778
898
  return;
899
+ }
779
900
  if (((_b = test.result) == null ? void 0 : _b.state) === "fail") {
780
901
  updateTask(test, runner);
781
902
  return;
@@ -795,29 +916,46 @@ async function runTest(test, runner) {
795
916
  for (let retryCount = 0; retryCount <= retry; retryCount++) {
796
917
  let beforeEachCleanups = [];
797
918
  try {
798
- await ((_c = runner.onBeforeTryTask) == null ? void 0 : _c.call(runner, test, { retry: retryCount, repeats: repeatCount }));
919
+ await ((_c = runner.onBeforeTryTask) == null ? void 0 : _c.call(runner, test, {
920
+ retry: retryCount,
921
+ repeats: repeatCount
922
+ }));
799
923
  test.result.repeatCount = repeatCount;
800
- beforeEachCleanups = await callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]);
924
+ beforeEachCleanups = await callSuiteHook(
925
+ suite,
926
+ test,
927
+ "beforeEach",
928
+ runner,
929
+ [test.context, suite]
930
+ );
801
931
  if (runner.runTask) {
802
932
  await runner.runTask(test);
803
933
  } else {
804
934
  const fn = getFn(test);
805
- if (!fn)
806
- throw new Error("Test function is not found. Did you add it using `setFn`?");
935
+ if (!fn) {
936
+ throw new Error(
937
+ "Test function is not found. Did you add it using `setFn`?"
938
+ );
939
+ }
807
940
  await fn();
808
941
  }
809
942
  if (test.promises) {
810
943
  const result = await Promise.allSettled(test.promises);
811
944
  const errors = result.map((r) => r.status === "rejected" ? r.reason : void 0).filter(Boolean);
812
- if (errors.length)
945
+ if (errors.length) {
813
946
  throw errors;
947
+ }
814
948
  }
815
- await ((_d = runner.onAfterTryTask) == null ? void 0 : _d.call(runner, test, { retry: retryCount, repeats: repeatCount }));
949
+ await ((_d = runner.onAfterTryTask) == null ? void 0 : _d.call(runner, test, {
950
+ retry: retryCount,
951
+ repeats: repeatCount
952
+ }));
816
953
  if (test.result.state !== "fail") {
817
- if (!test.repeats)
954
+ if (!test.repeats) {
818
955
  test.result.state = "pass";
819
- else if (test.repeats && retry === retryCount)
956
+ } else if (test.repeats && retry === retryCount) {
820
957
  test.result.state = "pass";
958
+ }
821
959
  }
822
960
  } catch (e) {
823
961
  failTask(test.result, e, runner.config.diffOptions);
@@ -830,14 +968,18 @@ async function runTest(test, runner) {
830
968
  return;
831
969
  }
832
970
  try {
833
- await callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]);
971
+ await callSuiteHook(suite, test, "afterEach", runner, [
972
+ test.context,
973
+ suite
974
+ ]);
834
975
  await callCleanupHooks(beforeEachCleanups);
835
976
  await callFixtureCleanup(test.context);
836
977
  } catch (e) {
837
978
  failTask(test.result, e, runner.config.diffOptions);
838
979
  }
839
- if (test.result.state === "pass")
980
+ if (test.result.state === "pass") {
840
981
  break;
982
+ }
841
983
  if (retryCount < retry) {
842
984
  test.result.state = "run";
843
985
  test.result.retryCount = (test.result.retryCount ?? 0) + 1;
@@ -852,7 +994,11 @@ async function runTest(test, runner) {
852
994
  }
853
995
  if (test.result.state === "fail") {
854
996
  try {
855
- await callTaskHooks(test, test.onFailed || [], runner.config.sequence.hooks);
997
+ await callTaskHooks(
998
+ test,
999
+ test.onFailed || [],
1000
+ runner.config.sequence.hooks
1001
+ );
856
1002
  } catch (e) {
857
1003
  failTask(test.result, e, runner.config.diffOptions);
858
1004
  }
@@ -890,8 +1036,9 @@ function markTasksAsSkipped(suite, runner) {
890
1036
  t.mode = "skip";
891
1037
  t.result = { ...t.result, state: "skip" };
892
1038
  updateTask(t, runner);
893
- if (t.type === "suite")
1039
+ if (t.type === "suite") {
894
1040
  markTasksAsSkipped(t, runner);
1041
+ }
895
1042
  });
896
1043
  }
897
1044
  async function runSuite(suite, runner) {
@@ -915,24 +1062,34 @@ async function runSuite(suite, runner) {
915
1062
  suite.result.state = "todo";
916
1063
  } else {
917
1064
  try {
918
- beforeAllCleanups = await callSuiteHook(suite, suite, "beforeAll", runner, [suite]);
1065
+ beforeAllCleanups = await callSuiteHook(
1066
+ suite,
1067
+ suite,
1068
+ "beforeAll",
1069
+ runner,
1070
+ [suite]
1071
+ );
919
1072
  if (runner.runSuite) {
920
1073
  await runner.runSuite(suite);
921
1074
  } else {
922
1075
  for (let tasksGroup of partitionSuiteChildren(suite)) {
923
1076
  if (tasksGroup[0].concurrent === true) {
924
- const mutex = limit(runner.config.maxConcurrency);
925
- await Promise.all(tasksGroup.map((c) => mutex(() => runSuiteChild(c, runner))));
1077
+ await Promise.all(tasksGroup.map((c) => runSuiteChild(c, runner)));
926
1078
  } else {
927
1079
  const { sequence } = runner.config;
928
1080
  if (sequence.shuffle || suite.shuffle) {
929
- const suites = tasksGroup.filter((group) => group.type === "suite");
1081
+ const suites = tasksGroup.filter(
1082
+ (group) => group.type === "suite"
1083
+ );
930
1084
  const tests = tasksGroup.filter((group) => group.type === "test");
931
1085
  const groups = shuffle([suites, tests], sequence.seed);
932
- tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
1086
+ tasksGroup = groups.flatMap(
1087
+ (group) => shuffle(group, sequence.seed)
1088
+ );
933
1089
  }
934
- for (const c of tasksGroup)
1090
+ for (const c of tasksGroup) {
935
1091
  await runSuiteChild(c, runner);
1092
+ }
936
1093
  }
937
1094
  }
938
1095
  }
@@ -949,7 +1106,9 @@ async function runSuite(suite, runner) {
949
1106
  if (!runner.config.passWithNoTests && !hasTests(suite)) {
950
1107
  suite.result.state = "fail";
951
1108
  if (!((_c = suite.result.errors) == null ? void 0 : _c.length)) {
952
- const error = processError(new Error(`No test found in suite ${suite.name}`));
1109
+ const error = processError(
1110
+ new Error(`No test found in suite ${suite.name}`)
1111
+ );
953
1112
  suite.result.errors = [error];
954
1113
  }
955
1114
  } else if (hasFailed(suite)) {
@@ -963,18 +1122,23 @@ async function runSuite(suite, runner) {
963
1122
  await ((_d = runner.onAfterRunSuite) == null ? void 0 : _d.call(runner, suite));
964
1123
  }
965
1124
  }
1125
+ let limitMaxConcurrency;
966
1126
  async function runSuiteChild(c, runner) {
967
- if (c.type === "test" || c.type === "custom")
968
- return runTest(c, runner);
969
- else if (c.type === "suite")
1127
+ if (c.type === "test" || c.type === "custom") {
1128
+ return limitMaxConcurrency(() => runTest(c, runner));
1129
+ } else if (c.type === "suite") {
970
1130
  return runSuite(c, runner);
1131
+ }
971
1132
  }
972
1133
  async function runFiles(files, runner) {
973
1134
  var _a, _b;
1135
+ limitMaxConcurrency ?? (limitMaxConcurrency = limit(runner.config.maxConcurrency));
974
1136
  for (const file of files) {
975
1137
  if (!file.tasks.length && !runner.config.passWithNoTests) {
976
1138
  if (!((_b = (_a = file.result) == null ? void 0 : _a.errors) == null ? void 0 : _b.length)) {
977
- const error = processError(new Error(`No test suite found in file ${file.filepath}`));
1139
+ const error = processError(
1140
+ new Error(`No test suite found in file ${file.filepath}`)
1141
+ );
978
1142
  file.result = {
979
1143
  state: "fail",
980
1144
  errors: [error]
@@ -1000,30 +1164,49 @@ function getDefaultHookTimeout() {
1000
1164
  return getRunner().config.hookTimeout;
1001
1165
  }
1002
1166
  function beforeAll(fn, timeout) {
1003
- return getCurrentSuite().on("beforeAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
1167
+ return getCurrentSuite().on(
1168
+ "beforeAll",
1169
+ withTimeout(fn, timeout ?? getDefaultHookTimeout(), true)
1170
+ );
1004
1171
  }
1005
1172
  function afterAll(fn, timeout) {
1006
- return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
1173
+ return getCurrentSuite().on(
1174
+ "afterAll",
1175
+ withTimeout(fn, timeout ?? getDefaultHookTimeout(), true)
1176
+ );
1007
1177
  }
1008
1178
  function beforeEach(fn, timeout) {
1009
- return getCurrentSuite().on("beforeEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true));
1179
+ return getCurrentSuite().on(
1180
+ "beforeEach",
1181
+ withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true)
1182
+ );
1010
1183
  }
1011
1184
  function afterEach(fn, timeout) {
1012
- return getCurrentSuite().on("afterEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true));
1185
+ return getCurrentSuite().on(
1186
+ "afterEach",
1187
+ withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true)
1188
+ );
1013
1189
  }
1014
- const onTestFailed = createTestHook("onTestFailed", (test, handler) => {
1015
- test.onFailed || (test.onFailed = []);
1016
- test.onFailed.push(handler);
1017
- });
1018
- const onTestFinished = createTestHook("onTestFinished", (test, handler) => {
1019
- test.onFinished || (test.onFinished = []);
1020
- test.onFinished.push(handler);
1021
- });
1190
+ const onTestFailed = createTestHook(
1191
+ "onTestFailed",
1192
+ (test, handler) => {
1193
+ test.onFailed || (test.onFailed = []);
1194
+ test.onFailed.push(handler);
1195
+ }
1196
+ );
1197
+ const onTestFinished = createTestHook(
1198
+ "onTestFinished",
1199
+ (test, handler) => {
1200
+ test.onFinished || (test.onFinished = []);
1201
+ test.onFinished.push(handler);
1202
+ }
1203
+ );
1022
1204
  function createTestHook(name, handler) {
1023
1205
  return (fn) => {
1024
1206
  const current = getCurrentTest();
1025
- if (!current)
1207
+ if (!current) {
1026
1208
  throw new Error(`Hook ${name}() can only be called inside a test`);
1209
+ }
1027
1210
  return handler(current, fn);
1028
1211
  };
1029
1212
  }
@@ -64,7 +64,11 @@ interface TaskResult {
64
64
  retryCount?: number;
65
65
  repeatCount?: number;
66
66
  }
67
- type TaskResultPack = [id: string, result: TaskResult | undefined, meta: TaskMeta];
67
+ type TaskResultPack = [
68
+ id: string,
69
+ result: TaskResult | undefined,
70
+ meta: TaskMeta
71
+ ];
68
72
  interface Suite extends TaskBase {
69
73
  file: File;
70
74
  type: 'suite';
@@ -72,7 +76,7 @@ interface Suite extends TaskBase {
72
76
  }
73
77
  interface File extends Suite {
74
78
  filepath: string;
75
- projectName: string;
79
+ projectName: string | undefined;
76
80
  collectDuration?: number;
77
81
  setupDuration?: number;
78
82
  }
@@ -114,6 +118,14 @@ interface TestEachFunction {
114
118
  <T>(cases: ReadonlyArray<T>): EachFunctionReturn<T[]>;
115
119
  (...args: [TemplateStringsArray, ...any]): EachFunctionReturn<any[]>;
116
120
  }
121
+ interface TestForFunctionReturn<Arg, Context> {
122
+ (name: string | Function, fn: (arg: Arg, context: Context) => Awaitable<void>): void;
123
+ (name: string | Function, options: TestOptions, fn: (args: Arg, context: Context) => Awaitable<void>): void;
124
+ }
125
+ interface TestForFunction<ExtraContext> {
126
+ <T>(cases: ReadonlyArray<T>): TestForFunctionReturn<T, ExtendedContext<Test> & ExtraContext>;
127
+ (strings: TemplateStringsArray, ...values: any[]): TestForFunctionReturn<any, ExtendedContext<Test> & ExtraContext>;
128
+ }
117
129
  interface TestCollectorCallable<C = {}> {
118
130
  /**
119
131
  * @deprecated Use options as the second argument instead
@@ -124,6 +136,7 @@ interface TestCollectorCallable<C = {}> {
124
136
  }
125
137
  type ChainableTestAPI<ExtraContext = {}> = ChainableFunction<'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails', TestCollectorCallable<ExtraContext>, {
126
138
  each: TestEachFunction;
139
+ for: TestForFunction<ExtraContext>;
127
140
  }>;
128
141
  interface TestOptions {
129
142
  /**
@@ -145,7 +158,7 @@ interface TestOptions {
145
158
  */
146
159
  repeats?: number;
147
160
  /**
148
- * Whether tests run concurrently.
161
+ * Whether suites and tests run concurrently.
149
162
  * Tests inherit `concurrent` from `describe()` and nested `describe()` will inherit from parent's `concurrent`.
150
163
  */
151
164
  concurrent?: boolean;
@@ -193,7 +206,7 @@ interface FixtureOptions {
193
206
  }
194
207
  type Use<T> = (value: T) => Promise<void>;
195
208
  type FixtureFn<T, K extends keyof T, ExtraContext> = (context: Omit<T, K> & ExtraContext, use: Use<T[K]>) => Promise<void>;
196
- type Fixture<T, K extends keyof T, ExtraContext = {}> = ((...args: any) => any) extends T[K] ? (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never) : T[K] | (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never);
209
+ type Fixture<T, K extends keyof T, ExtraContext = {}> = ((...args: any) => any) extends T[K] ? T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never : T[K] | (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never);
197
210
  type Fixtures<T extends Record<string, any>, ExtraContext = {}> = {
198
211
  [K in keyof T]: Fixture<T, K, ExtraContext & ExtendedContext<Test>> | [Fixture<T, K, ExtraContext & ExtendedContext<Test>>, FixtureOptions?];
199
212
  };
@@ -218,8 +231,14 @@ type HookCleanupCallback = (() => Awaitable<unknown>) | void;
218
231
  interface SuiteHooks<ExtraContext = {}> {
219
232
  beforeAll: HookListener<[Readonly<Suite | File>], HookCleanupCallback>[];
220
233
  afterAll: HookListener<[Readonly<Suite | File>]>[];
221
- beforeEach: HookListener<[ExtendedContext<Test | Custom> & ExtraContext, Readonly<Suite>], HookCleanupCallback>[];
222
- afterEach: HookListener<[ExtendedContext<Test | Custom> & ExtraContext, Readonly<Suite>]>[];
234
+ beforeEach: HookListener<[
235
+ ExtendedContext<Test | Custom> & ExtraContext,
236
+ Readonly<Suite>
237
+ ], HookCleanupCallback>[];
238
+ afterEach: HookListener<[
239
+ ExtendedContext<Test | Custom> & ExtraContext,
240
+ Readonly<Suite>
241
+ ]>[];
223
242
  }
224
243
  interface TaskCustomOptions extends TestOptions {
225
244
  concurrent?: boolean;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as SequenceHooks, G as SequenceSetupFiles, F as File, T as Task, S as Suite, o as TaskResultPack, a as Test, C as Custom, A as TaskContext, E as ExtendedContext } from './tasks-Ck0GpLiZ.js';
2
- export { g as CustomAPI, D as DoneCallback, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, O as OnTestFailedHandler, i as OnTestFinishedHandler, R as RunMode, y as RuntimeContext, d as SuiteAPI, f as SuiteCollector, x as SuiteFactory, h as SuiteHooks, k as TaskBase, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, j as TaskState, e as TestAPI, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-Ck0GpLiZ.js';
1
+ import { B as SequenceHooks, G as SequenceSetupFiles, F as File, T as Task, S as Suite, o as TaskResultPack, a as Test, C as Custom, A as TaskContext, E as ExtendedContext } from './tasks-BP89OzIP.js';
2
+ export { g as CustomAPI, D as DoneCallback, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, O as OnTestFailedHandler, i as OnTestFinishedHandler, R as RunMode, y as RuntimeContext, d as SuiteAPI, f as SuiteCollector, x as SuiteFactory, h as SuiteHooks, k as TaskBase, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, j as TaskState, e as TestAPI, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-BP89OzIP.js';
3
3
  import { DiffOptions } from '@vitest/utils/diff';
4
4
  import '@vitest/utils';
5
5
 
@@ -31,12 +31,16 @@ type VitestRunnerImportSource = 'collect' | 'setup';
31
31
  interface VitestRunnerConstructor {
32
32
  new (config: VitestRunnerConfig): VitestRunner;
33
33
  }
34
- type CancelReason = 'keyboard-input' | 'test-failure' | string & Record<string, never>;
34
+ type CancelReason = 'keyboard-input' | 'test-failure' | (string & Record<string, never>);
35
35
  interface VitestRunner {
36
36
  /**
37
37
  * First thing that's getting called before actually collecting and running tests.
38
38
  */
39
39
  onBeforeCollect?: (paths: string[]) => unknown;
40
+ /**
41
+ * Called after the file task was created but not collected yet.
42
+ */
43
+ onCollectStart?: (file: File) => unknown;
40
44
  /**
41
45
  * Called after collecting tests and before "onBeforeRun".
42
46
  */
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Suite, T as Task, a as Test, C as Custom } from './tasks-Ck0GpLiZ.js';
2
- export { b as ChainableFunction, c as createChainable } from './tasks-Ck0GpLiZ.js';
1
+ import { S as Suite, F as File, T as Task, a as Test, C as Custom } from './tasks-BP89OzIP.js';
2
+ export { b as ChainableFunction, c as createChainable } from './tasks-BP89OzIP.js';
3
3
  import { Arrayable } from '@vitest/utils';
4
4
 
5
5
  /**
@@ -9,12 +9,14 @@ declare function interpretTaskModes(suite: Suite, namePattern?: string | RegExp,
9
9
  declare function someTasksAreOnly(suite: Suite): boolean;
10
10
  declare function generateHash(str: string): string;
11
11
  declare function calculateSuiteHash(parent: Suite): void;
12
+ declare function createFileTask(filepath: string, root: string, projectName: string): File;
12
13
 
13
14
  /**
14
15
  * Partition in tasks groups by consecutive concurrent
15
16
  */
16
17
  declare function partitionSuiteChildren(suite: Suite): Task[][];
17
18
 
19
+ declare function isAtomTest(s: Task): s is Test | Custom;
18
20
  declare function getTests(suite: Arrayable<Task>): (Test | Custom)[];
19
21
  declare function getTasks(tasks?: Arrayable<Task>): Task[];
20
22
  declare function getSuites(suite: Arrayable<Task>): Suite[];
@@ -22,4 +24,4 @@ declare function hasTests(suite: Arrayable<Suite>): boolean;
22
24
  declare function hasFailed(suite: Arrayable<Task>): boolean;
23
25
  declare function getNames(task: Task): string[];
24
26
 
25
- export { calculateSuiteHash, generateHash, getNames, getSuites, getTasks, getTests, hasFailed, hasTests, interpretTaskModes, partitionSuiteChildren, someTasksAreOnly };
27
+ export { calculateSuiteHash, createFileTask, generateHash, getNames, getSuites, getTasks, getTests, hasFailed, hasTests, interpretTaskModes, isAtomTest, partitionSuiteChildren, someTasksAreOnly };
package/dist/utils.js CHANGED
@@ -1,3 +1,4 @@
1
- export { c as calculateSuiteHash, j as createChainable, g as generateHash, f as getNames, d as getSuites, b as getTasks, a as getTests, e as hasFailed, h as hasTests, i as interpretTaskModes, p as partitionSuiteChildren, s as someTasksAreOnly } from './chunk-tasks.js';
1
+ export { c as calculateSuiteHash, l as createChainable, a as createFileTask, g as generateHash, k as getNames, f as getSuites, e as getTasks, d as getTests, j as hasFailed, h as hasTests, i as interpretTaskModes, b as isAtomTest, p as partitionSuiteChildren, s as someTasksAreOnly } from './chunk-tasks.js';
2
2
  import '@vitest/utils/error';
3
+ import 'pathe';
3
4
  import '@vitest/utils';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/runner",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.1",
4
+ "version": "2.0.0-beta.11",
5
5
  "description": "Vitest test runner",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -40,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "p-limit": "^5.0.0",
42
42
  "pathe": "^1.1.2",
43
- "@vitest/utils": "2.0.0-beta.1"
43
+ "@vitest/utils": "2.0.0-beta.11"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "rimraf dist && rollup -c",