vitest 5.0.0-beta.4 → 5.0.0-beta.5

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 (54) hide show
  1. package/LICENSE.md +0 -17
  2. package/dist/browser.d.ts +8 -6
  3. package/dist/browser.js +5 -3
  4. package/dist/chunks/artifact.C0OHWdod.js +2770 -0
  5. package/dist/chunks/{base.BEGVMQrS.js → base.BPbmtl-C.js} +12 -12
  6. package/dist/chunks/browser.d.B-p-y0sB.d.ts +64 -0
  7. package/dist/chunks/{cac.CyXAEMkE.js → cac.DYQRhgb-.js} +13 -5
  8. package/dist/chunks/{cli-api.DJMXq34b.js → cli-api.SMst4llK.js} +217 -782
  9. package/dist/chunks/config.d.Bkl6J5iR.d.ts +2916 -0
  10. package/dist/chunks/{console.B3IRP8fX.js → console.DjVIMaXT.js} +4 -4
  11. package/dist/chunks/{coverage.d.g2xbl2sP.d.ts → coverage.d.Dn6XbyQq.d.ts} +2 -0
  12. package/dist/chunks/{creator.D66cVXYh.js → creator.CJiHagNv.js} +19 -4
  13. package/dist/chunks/{globals.BuY-yD0m.js → globals.D2wDMk06.js} +10 -10
  14. package/dist/chunks/{index.og1WyBLx.js → index.6dZyLbho.js} +618 -58
  15. package/dist/chunks/{index.CE58PZNH.js → index.CXzZ7KB8.js} +20 -5
  16. package/dist/chunks/{nativeModuleRunner.BOeMnHl4.js → index.CesbTg1C.js} +3 -34
  17. package/dist/chunks/{index.CcluKS59.js → index.DugGyGng.js} +5 -5
  18. package/dist/chunks/{index.nQFVd50u.js → index.DzNNaFWy.js} +10 -3
  19. package/dist/chunks/{init-forks.Ce3vGWgL.js → init-forks.CDWjf0q6.js} +1 -1
  20. package/dist/chunks/{init-threads.8e1OLv5v.js → init-threads.B-vZ8N2q.js} +1 -1
  21. package/dist/chunks/{init.6qx-LaHs.js → init.gslB1aqB.js} +9 -5
  22. package/dist/chunks/{nativeModuleMocker.DDZfQXLs.js → nativeModuleMocker.CrcWvO03.js} +2 -3
  23. package/dist/chunks/nativeModuleRunner.WlMdOB52.js +36 -0
  24. package/dist/chunks/{node.CwFbQqI1.js → node.C9I1sbE9.js} +1 -2
  25. package/dist/chunks/{plugin.d.B7MTG_Fe.d.ts → plugin.d.BuiCQSo8.d.ts} +48 -12
  26. package/dist/chunks/{rpc.DFRWVnRh.js → rpc.DZEh5xnQ.js} +1 -1
  27. package/dist/chunks/{rpc.d.OQ_EZi1Z.d.ts → rpc.d.BLxNuPAq.d.ts} +1 -2
  28. package/dist/chunks/{setup-common.DdEF_hkE.js → setup-common.DRlwpHsL.js} +2 -2
  29. package/dist/chunks/{global.d.BtKPuz2X.d.ts → task-utils.d.CLWcZaUf.d.ts} +13 -91
  30. package/dist/chunks/{utils.BX5Fg8C4.js → utils.DYj33du9.js} +1 -17
  31. package/dist/chunks/{vm.Bu7mmcZq.js → vm._FqKqltU.js} +4 -4
  32. package/dist/chunks/{worker.d.yR22cs6X.d.ts → worker.d.Cb0Z-SnE.d.ts} +3 -3
  33. package/dist/cli.js +1 -1
  34. package/dist/config.d.ts +12 -13
  35. package/dist/index.d.ts +188 -18
  36. package/dist/index.js +6 -6
  37. package/dist/module-evaluator.d.ts +7 -4
  38. package/dist/node.d.ts +16 -14
  39. package/dist/node.js +20 -15
  40. package/dist/runtime.d.ts +8 -2
  41. package/dist/runtime.js +4 -5
  42. package/dist/task-utils.js +296 -0
  43. package/dist/worker.d.ts +7 -6
  44. package/dist/worker.js +15 -14
  45. package/dist/workers/forks.js +16 -15
  46. package/dist/workers/runVmTests.js +8 -8
  47. package/dist/workers/threads.js +16 -15
  48. package/dist/workers/vmForks.js +8 -8
  49. package/dist/workers/vmThreads.js +8 -8
  50. package/package.json +14 -15
  51. package/dist/chunks/browser.d.BGxB4Xum.d.ts +0 -878
  52. package/dist/chunks/config.d.DXq1aBpy.d.ts +0 -244
  53. package/dist/chunks/environment.d-DOJxxZV9.d.DOJxxZV9.d.ts +0 -17
  54. package/dist/chunks/general.d.DFAHgpC2.d.ts +0 -247
@@ -0,0 +1,2770 @@
1
+ import { processError } from '@vitest/utils/error';
2
+ import { isObject, filterOutComments, ordinal, createDefer, assertTypes, toArray, isNegativeNaN, unique, objectAttr, shuffle } from '@vitest/utils/helpers';
3
+ import { getSafeTimers } from '@vitest/utils/timers';
4
+ import { createTaskName, createFileTask, calculateSuiteHash, interpretTaskModes, someTasksAreOnly, hasTests, hasFailed } from '../task-utils.js';
5
+ import { format, formatRegExp, truncateString, inspect } from '@vitest/utils/display';
6
+ import { parseSingleStack } from '@vitest/utils/source-map';
7
+
8
+ let _test;
9
+ function setCurrentTest(test) {
10
+ _test = test;
11
+ }
12
+ function getCurrentTest() {
13
+ return _test;
14
+ }
15
+ const tests = [];
16
+ function addRunningTest(test) {
17
+ tests.push(test);
18
+ return () => {
19
+ tests.splice(tests.indexOf(test));
20
+ };
21
+ }
22
+ function getRunningTests() {
23
+ return tests;
24
+ }
25
+
26
+ class PendingError extends Error {
27
+ code = "VITEST_PENDING";
28
+ taskId;
29
+ constructor(message, task, note) {
30
+ super(message);
31
+ this.message = message;
32
+ this.note = note;
33
+ this.taskId = task.id;
34
+ }
35
+ }
36
+ class TestRunAbortError extends Error {
37
+ name = "TestRunAbortError";
38
+ reason;
39
+ constructor(message, reason) {
40
+ super(message);
41
+ this.reason = reason;
42
+ }
43
+ }
44
+ class FixtureDependencyError extends Error {
45
+ name = "FixtureDependencyError";
46
+ }
47
+ class FixtureAccessError extends Error {
48
+ name = "FixtureAccessError";
49
+ }
50
+ class FixtureParseError extends Error {
51
+ name = "FixtureParseError";
52
+ }
53
+ class AroundHookSetupError extends Error {
54
+ name = "AroundHookSetupError";
55
+ }
56
+ class AroundHookTeardownError extends Error {
57
+ name = "AroundHookTeardownError";
58
+ }
59
+ class AroundHookMultipleCallsError extends Error {
60
+ name = "AroundHookMultipleCallsError";
61
+ }
62
+ // `test.fails` doesn't flip the test result when this error is thrown
63
+ class TestSyntaxError extends Error {
64
+ name = "TestSyntaxError";
65
+ constructor(message) {
66
+ super(message);
67
+ // use custom property so this survives when the error
68
+ // is serialized on `packages/expect` side (e.g. for `expect.soft`)
69
+ // and the runner can still detect it during `test.fails` handling
70
+ Object.defineProperty(this, "__vitest_test_syntax_error__", {
71
+ value: true,
72
+ enumerable: false
73
+ });
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Return a function for running multiple async operations with limited concurrency.
79
+ */
80
+ function limitConcurrency(concurrency = Infinity) {
81
+ // The number of currently active + pending tasks.
82
+ let count = 0;
83
+ // The head and tail of the pending task queue, built using a singly linked list.
84
+ // Both head and tail are initially undefined, signifying an empty queue.
85
+ // They both become undefined again whenever there are no pending tasks.
86
+ let head;
87
+ let tail;
88
+ // A bookkeeping function executed whenever a task has been run to completion.
89
+ const finish = () => {
90
+ count--;
91
+ // Check if there are further pending tasks in the queue.
92
+ if (head) {
93
+ // Allow the next pending task to run and pop it from the queue.
94
+ head[0]();
95
+ head = head[1];
96
+ // The head may now be undefined if there are no further pending tasks.
97
+ // In that case, set tail to undefined as well.
98
+ tail = head && tail;
99
+ }
100
+ };
101
+ const acquire = () => {
102
+ let released = false;
103
+ const release = () => {
104
+ if (!released) {
105
+ released = true;
106
+ finish();
107
+ }
108
+ };
109
+ if (count++ < concurrency) return release;
110
+ return new Promise((resolve) => {
111
+ if (tail)
112
+ // There are pending tasks, so append to the queue.
113
+ tail = tail[1] = [() => resolve(release)];
114
+ else
115
+ // No other pending tasks, initialize the queue with a new tail and head.
116
+ head = tail = [() => resolve(release)];
117
+ });
118
+ };
119
+ const limiterFn = (func, ...args) => {
120
+ function run(release) {
121
+ try {
122
+ const result = func(...args);
123
+ if (result instanceof Promise) return result.finally(release);
124
+ release();
125
+ return Promise.resolve(result);
126
+ } catch (error) {
127
+ release();
128
+ return Promise.reject(error);
129
+ }
130
+ }
131
+ const release = acquire();
132
+ return release instanceof Promise ? release.then(run) : run(release);
133
+ };
134
+ return Object.assign(limiterFn, { acquire });
135
+ }
136
+
137
+ // use WeakMap here to make the Test and Suite object serializable
138
+ const fnMap = /* @__PURE__ */ new WeakMap();
139
+ const testFixtureMap = /* @__PURE__ */ new WeakMap();
140
+ const hooksMap = /* @__PURE__ */ new WeakMap();
141
+ function setFn(key, fn) {
142
+ fnMap.set(key, fn);
143
+ }
144
+ function getFn(key) {
145
+ return fnMap.get(key);
146
+ }
147
+ function setTestFixture(key, fixture) {
148
+ testFixtureMap.set(key, fixture);
149
+ }
150
+ function getTestFixtures(key) {
151
+ return testFixtureMap.get(key);
152
+ }
153
+ function setHooks(key, hooks) {
154
+ hooksMap.set(key, hooks);
155
+ }
156
+ function getHooks(key) {
157
+ return hooksMap.get(key);
158
+ }
159
+
160
+ const FIXTURE_STACK_TRACE_KEY = Symbol.for("VITEST_FIXTURE_STACK_TRACE");
161
+ class TestFixtures {
162
+ _suiteContexts;
163
+ _overrides = /* @__PURE__ */ new WeakMap();
164
+ _registrations;
165
+ static _definitions = [];
166
+ static _builtinFixtures = [
167
+ "task",
168
+ "signal",
169
+ "onTestFailed",
170
+ "onTestFinished",
171
+ "skip",
172
+ "annotate",
173
+ "bench"
174
+ ];
175
+ static _fixtureOptionKeys = [
176
+ "auto",
177
+ "injected",
178
+ "scope"
179
+ ];
180
+ static _fixtureScopes = [
181
+ "test",
182
+ "file",
183
+ "worker"
184
+ ];
185
+ static _workerContextSuite = { type: "worker" };
186
+ static clearDefinitions() {
187
+ TestFixtures._definitions.length = 0;
188
+ }
189
+ static getWorkerContexts() {
190
+ return TestFixtures._definitions.map((f) => f.getWorkerContext());
191
+ }
192
+ static getFileContexts(file) {
193
+ return TestFixtures._definitions.map((f) => f.getFileContext(file));
194
+ }
195
+ static isFixtureOptions(obj) {
196
+ return isObject(obj) && Object.keys(obj).some((key) => TestFixtures._fixtureOptionKeys.includes(key));
197
+ }
198
+ constructor(registrations) {
199
+ this._registrations = registrations ?? /* @__PURE__ */ new Map();
200
+ this._suiteContexts = /* @__PURE__ */ new WeakMap();
201
+ TestFixtures._definitions.push(this);
202
+ }
203
+ extend(runner, userFixtures) {
204
+ const { suite } = getCurrentSuite();
205
+ const isTopLevel = !suite || suite.file === suite;
206
+ return new TestFixtures(this.parseUserFixtures(runner, userFixtures, isTopLevel));
207
+ }
208
+ get(suite) {
209
+ let currentSuite = suite;
210
+ while (currentSuite) {
211
+ const overrides = this._overrides.get(currentSuite);
212
+ // return the closest override
213
+ if (overrides) return overrides;
214
+ if (currentSuite === currentSuite.file) break;
215
+ currentSuite = currentSuite.suite || currentSuite.file;
216
+ }
217
+ return this._registrations;
218
+ }
219
+ override(runner, userFixtures) {
220
+ const { suite: currentSuite, file } = getCurrentSuite();
221
+ const suite = currentSuite || file;
222
+ const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
223
+ // Create a copy of the closest parent's registrations to avoid modifying them
224
+ // For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
225
+ const suiteRegistrations = new Map(this.get(suite));
226
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
227
+ // If defined in top-level, just override all registrations
228
+ // We don't support overriding suite-level fixtures anyway (it will throw an error)
229
+ if (isTopLevel) this._registrations = registrations;
230
+ else this._overrides.set(suite, registrations);
231
+ }
232
+ getFileContext(file) {
233
+ if (!this._suiteContexts.has(file)) this._suiteContexts.set(file, Object.create(null));
234
+ return this._suiteContexts.get(file);
235
+ }
236
+ getWorkerContext() {
237
+ if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
238
+ return this._suiteContexts.get(TestFixtures._workerContextSuite);
239
+ }
240
+ parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
241
+ const errors = [];
242
+ Object.entries(userFixtures).forEach(([name, fn]) => {
243
+ let options;
244
+ let value;
245
+ let _options;
246
+ if (Array.isArray(fn) && fn.length >= 2 && TestFixtures.isFixtureOptions(fn[1])) {
247
+ _options = fn[1];
248
+ options = {
249
+ auto: _options.auto ?? false,
250
+ scope: _options.scope ?? "test",
251
+ injected: _options.injected ?? false
252
+ };
253
+ value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
254
+ } else value = fn;
255
+ const parent = registrations.get(name);
256
+ if (parent && options) {
257
+ if (parent.scope !== options.scope) errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
258
+ if (parent.auto !== options.auto) errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
259
+ } else if (parent) options = {
260
+ auto: parent.auto,
261
+ scope: parent.scope,
262
+ injected: parent.injected
263
+ };
264
+ else if (!options) options = {
265
+ auto: false,
266
+ injected: false,
267
+ scope: "test"
268
+ };
269
+ if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
270
+ if (!supportNonTest && options.scope !== "test") errors.push(new FixtureDependencyError(`The "${name}" fixture cannot be defined with a ${options.scope} scope${!_options?.scope && parent?.scope ? " (inherited from the base fixture)" : ""} inside the describe block. Define it at the top level of the file instead.`));
271
+ const deps = isFixtureFunction(value) ? getUsedProps(value) : /* @__PURE__ */ new Set();
272
+ const item = {
273
+ name,
274
+ value,
275
+ auto: options.auto ?? false,
276
+ injected: options.injected ?? false,
277
+ scope: options.scope ?? "test",
278
+ deps,
279
+ parent
280
+ };
281
+ if (isFixtureFunction(value)) Object.assign(value, { [FIXTURE_STACK_TRACE_KEY]: /* @__PURE__ */ new Error("STACK_TRACE_ERROR") });
282
+ registrations.set(name, item);
283
+ if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) item.scope = "file";
284
+ });
285
+ // validate fixture dependency scopes
286
+ for (const fixture of registrations.values()) for (const depName of fixture.deps) {
287
+ if (TestFixtures._builtinFixtures.includes(depName)) continue;
288
+ const dep = registrations.get(depName);
289
+ if (!dep) {
290
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
291
+ continue;
292
+ }
293
+ if (depName === fixture.name && !fixture.parent) {
294
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
295
+ continue;
296
+ }
297
+ if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
298
+ errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
299
+ continue;
300
+ }
301
+ }
302
+ if (errors.length === 1) throw errors[0];
303
+ else if (errors.length > 1) throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
304
+ return registrations;
305
+ }
306
+ }
307
+ const cleanupFnArrayMap = /* @__PURE__ */ new WeakMap();
308
+ async function callFixtureCleanup(context) {
309
+ const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
310
+ for (const cleanup of cleanupFnArray.reverse()) await cleanup();
311
+ cleanupFnArrayMap.delete(context);
312
+ }
313
+ /**
314
+ * Returns the current number of cleanup functions registered for the context.
315
+ * This can be used as a checkpoint to later clean up only fixtures added after this point.
316
+ */
317
+ function getFixtureCleanupCount(context) {
318
+ return cleanupFnArrayMap.get(context)?.length ?? 0;
319
+ }
320
+ /**
321
+ * Cleans up only fixtures that were added after the given checkpoint index.
322
+ * This is used by aroundEach to clean up fixtures created inside runTest()
323
+ * while preserving fixtures that were created for aroundEach itself.
324
+ */
325
+ async function callFixtureCleanupFrom(context, fromIndex) {
326
+ const cleanupFnArray = cleanupFnArrayMap.get(context);
327
+ if (!cleanupFnArray || cleanupFnArray.length <= fromIndex) return;
328
+ // Get items added after the checkpoint
329
+ const toCleanup = cleanupFnArray.slice(fromIndex);
330
+ // Clean up in reverse order
331
+ for (const cleanup of toCleanup.reverse()) await cleanup();
332
+ // Remove cleaned up items from the array, keeping items before checkpoint
333
+ cleanupFnArray.length = fromIndex;
334
+ }
335
+ const contextHasFixturesCache = /* @__PURE__ */ new WeakMap();
336
+ function withFixtures(fn, options) {
337
+ const collector = getCurrentSuite();
338
+ const suite = options?.suite || collector.suite || collector.file;
339
+ return async (hookContext) => {
340
+ const context = hookContext || options?.context;
341
+ if (!context) {
342
+ if (options?.suiteHook) validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
343
+ return fn({});
344
+ }
345
+ const fixtures = options?.fixtures || getTestFixtures(context);
346
+ if (!fixtures) return fn(context);
347
+ const registrations = fixtures.get(suite);
348
+ if (!registrations.size) return fn(context);
349
+ const usedFixtures = [];
350
+ const usedProps = getUsedProps(fn);
351
+ for (const fixture of registrations.values()) if (isAutoFixture(fixture, options) || usedProps.has(fixture.name)) usedFixtures.push(fixture);
352
+ if (!usedFixtures.length) return fn(context);
353
+ if (!cleanupFnArrayMap.has(context)) cleanupFnArrayMap.set(context, []);
354
+ const cleanupFnArray = cleanupFnArrayMap.get(context);
355
+ const pendingFixtures = resolveDeps(usedFixtures, registrations);
356
+ if (!pendingFixtures.length) return fn(context);
357
+ // Check if suite-level hook is trying to access test-scoped fixtures
358
+ // Suite hooks (beforeAll/afterAll/aroundAll) can only access file/worker scoped fixtures
359
+ if (options?.suiteHook) {
360
+ const testScopedFixtures = pendingFixtures.filter((f) => f.scope === "test");
361
+ if (testScopedFixtures.length > 0) {
362
+ const fixtureNames = testScopedFixtures.map((f) => `"${f.name}"`).join(", ");
363
+ const error = new FixtureDependencyError(`Test-scoped fixtures cannot be used inside ${options.suiteHook} hook. The following fixtures are test-scoped: ${fixtureNames}. Use { scope: 'file' } or { scope: 'worker' } fixtures instead, or move the logic to ${{
364
+ aroundAll: "aroundEach",
365
+ beforeAll: "beforeEach",
366
+ afterAll: "afterEach"
367
+ }[options.suiteHook]} hook.`);
368
+ // Use stack trace from hook registration for better error location
369
+ if (options.stackTraceError?.stack) error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
370
+ throw error;
371
+ }
372
+ }
373
+ if (!contextHasFixturesCache.has(context)) contextHasFixturesCache.set(context, /* @__PURE__ */ new WeakSet());
374
+ const cachedFixtures = contextHasFixturesCache.get(context);
375
+ for (const fixture of pendingFixtures) if (fixture.scope === "test") {
376
+ // fixture could be already initialized during "before" hook
377
+ // we can't check "fixture.name" in context because context may
378
+ // access the parent fixture ({ a: ({ a }) => {} })
379
+ if (cachedFixtures.has(fixture)) continue;
380
+ cachedFixtures.add(fixture);
381
+ const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
382
+ context[fixture.name] = resolvedValue;
383
+ cleanupFnArray.push(() => {
384
+ cachedFixtures.delete(fixture);
385
+ });
386
+ } else {
387
+ const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
388
+ context[fixture.name] = resolvedValue;
389
+ }
390
+ return fn(context);
391
+ };
392
+ }
393
+ function isAutoFixture(fixture, options) {
394
+ if (!fixture.auto) return false;
395
+ // suite hook doesn't automatically trigger unused test-scoped fixtures.
396
+ if (options?.suiteHook && fixture.scope === "test") return false;
397
+ return true;
398
+ }
399
+ function isFixtureFunction(value) {
400
+ return typeof value === "function";
401
+ }
402
+ function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
403
+ if (!isFixtureFunction(fixture.value)) return fixture.value;
404
+ return resolveFixtureFunction(fixture.value, fixture.name, context, cleanupFnArray);
405
+ }
406
+ const scopedFixturePromiseCache = /* @__PURE__ */ new WeakMap();
407
+ async function resolveScopeFixtureValue(fixtures, suite, fixture) {
408
+ const workerContext = fixtures.getWorkerContext();
409
+ const fileContext = fixtures.getFileContext(suite.file);
410
+ const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
411
+ if (!isFixtureFunction(fixture.value)) {
412
+ fixtureContext[fixture.name] = fixture.value;
413
+ return fixture.value;
414
+ }
415
+ if (fixture.name in fixtureContext) return fixtureContext[fixture.name];
416
+ if (scopedFixturePromiseCache.has(fixture)) return scopedFixturePromiseCache.get(fixture);
417
+ if (!cleanupFnArrayMap.has(fixtureContext)) cleanupFnArrayMap.set(fixtureContext, []);
418
+ const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
419
+ const promise = resolveFixtureFunction(fixture.value, fixture.name, fixture.scope === "file" ? {
420
+ ...workerContext,
421
+ ...fileContext
422
+ } : fixtureContext, cleanupFnFileArray).then((value) => {
423
+ fixtureContext[fixture.name] = value;
424
+ scopedFixturePromiseCache.delete(fixture);
425
+ return value;
426
+ });
427
+ scopedFixturePromiseCache.set(fixture, promise);
428
+ return promise;
429
+ }
430
+ async function resolveFixtureFunction(fixtureFn, fixtureName, context, cleanupFnArray) {
431
+ // wait for `use` call to extract fixture value
432
+ const useFnArgPromise = createDefer();
433
+ const stackTraceError = FIXTURE_STACK_TRACE_KEY in fixtureFn && fixtureFn[FIXTURE_STACK_TRACE_KEY] instanceof Error ? fixtureFn[FIXTURE_STACK_TRACE_KEY] : void 0;
434
+ let isUseFnArgResolved = false;
435
+ const fixtureReturn = fixtureFn(context, async (useFnArg) => {
436
+ // extract `use` argument
437
+ isUseFnArgResolved = true;
438
+ useFnArgPromise.resolve(useFnArg);
439
+ // suspend fixture teardown by holding off `useReturnPromise` resolution until cleanup
440
+ const useReturnPromise = createDefer();
441
+ cleanupFnArray.push(async () => {
442
+ // start teardown by resolving `use` Promise
443
+ useReturnPromise.resolve();
444
+ // wait for finishing teardown
445
+ await fixtureReturn;
446
+ });
447
+ await useReturnPromise;
448
+ }).then(() => {
449
+ // fixture returned without calling use()
450
+ if (!isUseFnArgResolved) {
451
+ const error = /* @__PURE__ */ new Error(`Fixture "${fixtureName}" returned without calling "use". Make sure to call "use" in every code path of the fixture function.`);
452
+ if (stackTraceError?.stack) error.stack = error.message + stackTraceError.stack.replace(stackTraceError.message, "");
453
+ useFnArgPromise.reject(error);
454
+ }
455
+ }).catch((e) => {
456
+ // treat fixture setup error as test failure
457
+ if (!isUseFnArgResolved) {
458
+ useFnArgPromise.reject(e);
459
+ return;
460
+ }
461
+ // otherwise re-throw to avoid silencing error during cleanup
462
+ throw e;
463
+ });
464
+ return useFnArgPromise;
465
+ }
466
+ function resolveDeps(usedFixtures, registrations, depSet = /* @__PURE__ */ new Set(), pendingFixtures = []) {
467
+ usedFixtures.forEach((fixture) => {
468
+ if (pendingFixtures.includes(fixture)) return;
469
+ if (!isFixtureFunction(fixture.value) || !fixture.deps) {
470
+ pendingFixtures.push(fixture);
471
+ return;
472
+ }
473
+ if (depSet.has(fixture)) if (fixture.parent) fixture = fixture.parent;
474
+ else throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map((d) => d.name).join(" <- ")}`);
475
+ depSet.add(fixture);
476
+ resolveDeps([...fixture.deps].map((n) => n === fixture.name ? fixture.parent : registrations.get(n)).filter((n) => !!n), registrations, depSet, pendingFixtures);
477
+ pendingFixtures.push(fixture);
478
+ depSet.clear();
479
+ });
480
+ return pendingFixtures;
481
+ }
482
+ function validateSuiteHook(fn, hook, suiteError) {
483
+ const usedProps = getUsedProps(fn, {
484
+ sourceError: suiteError,
485
+ suiteHook: hook
486
+ });
487
+ if (usedProps.size) {
488
+ const error = new FixtureAccessError(`The ${hook} hook uses fixtures "${[...usedProps].join("\", \"")}", but has no access to context. Did you forget to call it as "test.${hook}()" instead of "${hook}()"?\nIf you used internal "suite" task as the first argument previously, access it in the second argument instead. See https://vitest.dev/guide/test-context#suite-level-hooks`);
489
+ if (suiteError) error.stack = suiteError.stack?.replace(suiteError.message, error.message);
490
+ throw error;
491
+ }
492
+ }
493
+ const kPropsSymbol = Symbol("$vitest:fixture-props");
494
+ const kPropNamesSymbol = Symbol("$vitest:fixture-prop-names");
495
+ function configureProps(fn, options) {
496
+ Object.defineProperty(fn, kPropsSymbol, {
497
+ value: options,
498
+ enumerable: false
499
+ });
500
+ }
501
+ function memoProps(fn, props) {
502
+ fn[kPropNamesSymbol] = props;
503
+ return props;
504
+ }
505
+ function getUsedProps(fn, { sourceError, suiteHook } = {}) {
506
+ if (kPropNamesSymbol in fn) return fn[kPropNamesSymbol];
507
+ const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
508
+ let fnString = filterOutComments(implementation.toString());
509
+ // match lowered async function and strip it off
510
+ // example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9LAoKICBoMTogYXN5bmMgKCkgPT4ge30sCiAgLy8gY29tbWVudCBiZXR3ZWVuCiAgaDI6IGFzeW5jIChhKSA9PiB7fSwKfQ
511
+ // __async(this, null, function*
512
+ // __async(this, arguments, function*
513
+ // __async(this, [_0, _1], function*
514
+ if (/__async\((?:this|null), (?:null|arguments|\[[_0-9, ]*\]), function\*/.test(fnString)) fnString = fnString.split(/__async\((?:this|null),/)[1];
515
+ const match = fnString.match(/[^(]*\(([^)]*)/);
516
+ if (!match) return memoProps(fn, /* @__PURE__ */ new Set());
517
+ const args = splitByComma(match[1]);
518
+ if (!args.length) return memoProps(fn, /* @__PURE__ */ new Set());
519
+ const fixturesArgument = args[fixturesIndex];
520
+ if (!fixturesArgument) return memoProps(fn, /* @__PURE__ */ new Set());
521
+ if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
522
+ const ordinalArgument = ordinal(fixturesIndex + 1);
523
+ const error = new FixtureParseError(`The ${ordinalArgument} argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "${fixturesArgument}".${suiteHook ? ` If you used internal "suite" task as the ${ordinalArgument} argument previously, access it in the ${ordinal(fixturesIndex + 2)} argument instead.` : ""}`);
524
+ if (sourceError) error.stack = sourceError.stack?.replace(sourceError.message, error.message);
525
+ throw error;
526
+ }
527
+ const props = splitByComma(fixturesArgument.slice(1, -1).replace(/\s/g, "")).map((prop) => {
528
+ return prop.replace(/:.*|=.*/g, "");
529
+ });
530
+ const last = props.at(-1);
531
+ if (last && last.startsWith("...")) {
532
+ const error = new FixtureParseError(`Rest parameters are not supported in fixtures, received "${last}".`);
533
+ if (sourceError) error.stack = sourceError.stack?.replace(sourceError.message, error.message);
534
+ throw error;
535
+ }
536
+ return memoProps(fn, new Set(props));
537
+ }
538
+ function splitByComma(s) {
539
+ const result = [];
540
+ const stack = [];
541
+ let start = 0;
542
+ for (let i = 0; i < s.length; i++) if (s[i] === "{" || s[i] === "[") stack.push(s[i] === "{" ? "}" : "]");
543
+ else if (s[i] === stack.at(-1)) stack.pop();
544
+ else if (!stack.length && s[i] === ",") {
545
+ const token = s.substring(start, i).trim();
546
+ if (token) result.push(token);
547
+ start = i + 1;
548
+ }
549
+ const lastToken = s.substring(start).trim();
550
+ if (lastToken) result.push(lastToken);
551
+ return result;
552
+ }
553
+
554
+ const kChainableContext = Symbol("kChainableContext");
555
+ function getChainableContext(chainable) {
556
+ return chainable?.[kChainableContext];
557
+ }
558
+ function createChainable(keys, fn, context) {
559
+ function create(context) {
560
+ const chain = function(...args) {
561
+ return fn.apply(context, args);
562
+ };
563
+ Object.assign(chain, fn);
564
+ Object.defineProperty(chain, kChainableContext, {
565
+ value: {
566
+ withContext: () => chain.bind(context),
567
+ getFixtures: () => context.fixtures,
568
+ setContext: (key, value) => {
569
+ context[key] = value;
570
+ },
571
+ mergeContext: (ctx) => {
572
+ Object.assign(context, ctx);
573
+ }
574
+ },
575
+ enumerable: false
576
+ });
577
+ for (const key of keys) Object.defineProperty(chain, key, { get() {
578
+ return create({
579
+ ...context,
580
+ [key]: true
581
+ });
582
+ } });
583
+ return chain;
584
+ }
585
+ const chain = create(context ?? {});
586
+ Object.defineProperty(chain, "fn", {
587
+ value: fn,
588
+ enumerable: false
589
+ });
590
+ return chain;
591
+ }
592
+
593
+ function getDefaultHookTimeout() {
594
+ return getRunner().config.hookTimeout;
595
+ }
596
+ const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
597
+ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
598
+ const AROUND_TIMEOUT_KEY = Symbol.for("VITEST_AROUND_TIMEOUT");
599
+ const AROUND_STACK_TRACE_KEY = Symbol.for("VITEST_AROUND_STACK_TRACE");
600
+ function getBeforeHookCleanupCallback(hook, result, context) {
601
+ if (typeof result === "function") return withTimeout(result, CLEANUP_TIMEOUT_KEY in hook && typeof hook[CLEANUP_TIMEOUT_KEY] === "number" ? hook[CLEANUP_TIMEOUT_KEY] : getDefaultHookTimeout(), true, CLEANUP_STACK_TRACE_KEY in hook && hook[CLEANUP_STACK_TRACE_KEY] instanceof Error ? hook[CLEANUP_STACK_TRACE_KEY] : void 0, (_, error) => {
602
+ if (context) abortContextSignal(context, error);
603
+ });
604
+ }
605
+ /**
606
+ * Registers a callback function to be executed once before all tests within the current suite.
607
+ * This hook is useful for scenarios where you need to perform setup operations that are common to all tests in a suite, such as initializing a database connection or setting up a test environment.
608
+ *
609
+ * **Note:** The `beforeAll` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
610
+ *
611
+ * @param {Function} fn - The callback function to be executed before all tests.
612
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
613
+ * @returns {void}
614
+ * @example
615
+ * ```ts
616
+ * // Example of using beforeAll to set up a database connection
617
+ * beforeAll(async () => {
618
+ * await database.connect();
619
+ * });
620
+ * ```
621
+ */
622
+ function beforeAll(fn, timeout = getDefaultHookTimeout()) {
623
+ assertTypes(fn, "\"beforeAll\" callback", ["function"]);
624
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
625
+ const context = getChainableContext(this);
626
+ return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
627
+ [CLEANUP_TIMEOUT_KEY]: timeout,
628
+ [CLEANUP_STACK_TRACE_KEY]: stackTraceError
629
+ }));
630
+ }
631
+ /**
632
+ * Registers a callback function to be executed once after all tests within the current suite have completed.
633
+ * This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
634
+ *
635
+ * **Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
636
+ *
637
+ * @param {Function} fn - The callback function to be executed after all tests.
638
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
639
+ * @returns {void}
640
+ * @example
641
+ * ```ts
642
+ * // Example of using afterAll to close a database connection
643
+ * afterAll(async () => {
644
+ * await database.disconnect();
645
+ * });
646
+ * ```
647
+ */
648
+ function afterAll(fn, timeout) {
649
+ assertTypes(fn, "\"afterAll\" callback", ["function"]);
650
+ const context = getChainableContext(this);
651
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
652
+ return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
653
+ }
654
+ /**
655
+ * Registers a callback function to be executed before each test within the current suite.
656
+ * This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables.
657
+ *
658
+ * **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
659
+ *
660
+ * @param {Function} fn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.
661
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
662
+ * @returns {void}
663
+ * @example
664
+ * ```ts
665
+ * // Example of using beforeEach to reset a database state
666
+ * beforeEach(async () => {
667
+ * await database.reset();
668
+ * });
669
+ * ```
670
+ */
671
+ function beforeEach(fn, timeout = getDefaultHookTimeout()) {
672
+ assertTypes(fn, "\"beforeEach\" callback", ["function"]);
673
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
674
+ const wrapper = (context, suite) => {
675
+ return withFixtures(fn, { suite })(context);
676
+ };
677
+ return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
678
+ [CLEANUP_TIMEOUT_KEY]: timeout,
679
+ [CLEANUP_STACK_TRACE_KEY]: stackTraceError
680
+ }));
681
+ }
682
+ /**
683
+ * Registers a callback function to be executed after each test within the current suite has completed.
684
+ * This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions.
685
+ *
686
+ * **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
687
+ *
688
+ * @param {Function} fn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.
689
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
690
+ * @returns {void}
691
+ * @example
692
+ * ```ts
693
+ * // Example of using afterEach to delete temporary files created during a test
694
+ * afterEach(async () => {
695
+ * await fileSystem.deleteTempFiles();
696
+ * });
697
+ * ```
698
+ */
699
+ function afterEach(fn, timeout) {
700
+ assertTypes(fn, "\"afterEach\" callback", ["function"]);
701
+ const wrapper = (context, suite) => {
702
+ return withFixtures(fn, { suite })(context);
703
+ };
704
+ return getCurrentSuite().on("afterEach", withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
705
+ }
706
+ /**
707
+ * Registers a callback function to be executed when a test fails within the current suite.
708
+ * This function allows for custom actions to be performed in response to test failures, such as logging, cleanup, or additional diagnostics.
709
+ *
710
+ * **Note:** The `onTestFailed` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
711
+ *
712
+ * @param {Function} fn - The callback function to be executed upon a test failure. The function receives the test result (including errors).
713
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
714
+ * @throws {Error} Throws an error if the function is not called within a test.
715
+ * @returns {void}
716
+ * @example
717
+ * ```ts
718
+ * // Example of using onTestFailed to log failure details
719
+ * onTestFailed(({ errors }) => {
720
+ * console.log(`Test failed: ${test.name}`, errors);
721
+ * });
722
+ * ```
723
+ */
724
+ const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
725
+ test.onFailed ||= [];
726
+ test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
727
+ });
728
+ /**
729
+ * Registers a callback function to be executed when the current test finishes, regardless of the outcome (pass or fail).
730
+ * This function is ideal for performing actions that should occur after every test execution, such as cleanup, logging, or resetting shared resources.
731
+ *
732
+ * This hook is useful if you have access to a resource in the test itself and you want to clean it up after the test finishes. It is a more compact way to clean up resources than using the combination of `beforeEach` and `afterEach`.
733
+ *
734
+ * **Note:** The `onTestFinished` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
735
+ *
736
+ * **Note:** The `onTestFinished` hook is not called if the test is canceled with a dynamic `ctx.skip()` call.
737
+ *
738
+ * @param {Function} fn - The callback function to be executed after a test finishes. The function can receive parameters providing details about the completed test, including its success or failure status.
739
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
740
+ * @throws {Error} Throws an error if the function is not called within a test.
741
+ * @returns {void}
742
+ * @example
743
+ * ```ts
744
+ * // Example of using onTestFinished for cleanup
745
+ * const db = await connectToDatabase();
746
+ * onTestFinished(async () => {
747
+ * await db.disconnect();
748
+ * });
749
+ * ```
750
+ */
751
+ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
752
+ test.onFinished ||= [];
753
+ test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
754
+ });
755
+ /**
756
+ * Registers a callback function that wraps around all tests within the current suite.
757
+ * The callback receives a `runSuite` function that must be called to run the suite's tests.
758
+ * This hook is useful for scenarios where you need to wrap an entire suite in a context
759
+ * (e.g., starting a server, opening a database connection that all tests share).
760
+ *
761
+ * **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
762
+ * The first registered hook is the outermost wrapper.
763
+ *
764
+ * @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
765
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
766
+ * @returns {void}
767
+ * @example
768
+ * ```ts
769
+ * // Example of using aroundAll to wrap suite in a tracing span
770
+ * aroundAll(async (runSuite) => {
771
+ * await tracer.trace('test-suite', runSuite);
772
+ * });
773
+ * ```
774
+ * @example
775
+ * ```ts
776
+ * // Example of using aroundAll with fixtures
777
+ * aroundAll(async (runSuite, { db }) => {
778
+ * await db.transaction(() => runSuite());
779
+ * });
780
+ * ```
781
+ */
782
+ function aroundAll(fn, timeout) {
783
+ assertTypes(fn, "\"aroundAll\" callback", ["function"]);
784
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
785
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
786
+ const context = getChainableContext(this);
787
+ return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
788
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
789
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
790
+ }));
791
+ }
792
+ /**
793
+ * Registers a callback function that wraps around each test within the current suite.
794
+ * The callback receives a `runTest` function that must be called to run the test.
795
+ * This hook is useful for scenarios where you need to wrap tests in a context (e.g., database transactions).
796
+ *
797
+ * **Note:** When multiple `aroundEach` hooks are registered, they are nested inside each other.
798
+ * The first registered hook is the outermost wrapper.
799
+ *
800
+ * @param {Function} fn - The callback function that wraps the test. Must call `runTest()` to run the test.
801
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
802
+ * @returns {void}
803
+ * @example
804
+ * ```ts
805
+ * // Example of using aroundEach to wrap tests in a database transaction
806
+ * aroundEach(async (runTest) => {
807
+ * await database.transaction(() => runTest());
808
+ * });
809
+ * ```
810
+ * @example
811
+ * ```ts
812
+ * // Example of using aroundEach with fixtures
813
+ * aroundEach(async (runTest, { db }) => {
814
+ * await db.transaction(() => runTest());
815
+ * });
816
+ * ```
817
+ */
818
+ function aroundEach(fn, timeout) {
819
+ assertTypes(fn, "\"aroundEach\" callback", ["function"]);
820
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
821
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
822
+ const wrapper = (runTest, context, suite) => {
823
+ const innerFn = (ctx) => fn(runTest, ctx, suite);
824
+ configureProps(innerFn, {
825
+ index: 1,
826
+ original: fn
827
+ });
828
+ return withFixtures(innerFn, { suite })(context);
829
+ };
830
+ return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
831
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
832
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
833
+ }));
834
+ }
835
+ function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
836
+ return (...args) => {
837
+ const suite = args.at(-1);
838
+ const prefix = args.slice(0, -1);
839
+ const wrapper = (ctx) => fn(...prefix, ctx, suite);
840
+ configureProps(wrapper, {
841
+ index: contextIndex,
842
+ original: fn
843
+ });
844
+ const fixtures = context?.getFixtures();
845
+ const fileContext = fixtures?.getFileContext(suite.file);
846
+ return withFixtures(wrapper, {
847
+ suiteHook,
848
+ fixtures,
849
+ context: fileContext,
850
+ stackTraceError
851
+ })();
852
+ };
853
+ }
854
+ function getAroundHookTimeout(hook) {
855
+ return AROUND_TIMEOUT_KEY in hook && typeof hook[AROUND_TIMEOUT_KEY] === "number" ? hook[AROUND_TIMEOUT_KEY] : getDefaultHookTimeout();
856
+ }
857
+ function getAroundHookStackTrace(hook) {
858
+ return AROUND_STACK_TRACE_KEY in hook && hook[AROUND_STACK_TRACE_KEY] instanceof Error ? hook[AROUND_STACK_TRACE_KEY] : void 0;
859
+ }
860
+ function createTestHook(name, handler) {
861
+ return (fn, timeout) => {
862
+ assertTypes(fn, `"${name}" callback`, ["function"]);
863
+ const current = getCurrentTest();
864
+ if (!current) throw new Error(`Hook ${name}() can only be called inside a test`);
865
+ return handler(current, fn, timeout);
866
+ };
867
+ }
868
+
869
+ function findTestFileStackTrace(testFilePath, error) {
870
+ // first line is the error message
871
+ const lines = error.split("\n").slice(1);
872
+ for (const line of lines) {
873
+ const stack = parseSingleStack(line);
874
+ if (stack && stack.file === testFilePath) return stack;
875
+ }
876
+ }
877
+
878
+ const filterMap = /* @__PURE__ */ new WeakMap();
879
+ /**
880
+ * @experimental
881
+ */
882
+ function matchesTags(testTags) {
883
+ const runner = getRunner();
884
+ const tagsFilter = runner._currentSpecification?.testTagsFilter ?? runner.config.tagsFilter;
885
+ if (!tagsFilter) return true;
886
+ let tagsFilterPredicate = filterMap.get(tagsFilter);
887
+ if (!tagsFilterPredicate) {
888
+ tagsFilterPredicate = createTagsFilter(tagsFilter, runner.config.tags);
889
+ filterMap.set(tagsFilter, tagsFilterPredicate);
890
+ }
891
+ return tagsFilterPredicate(testTags);
892
+ }
893
+ function validateTags(config, tags) {
894
+ if (!config.strictTags) return;
895
+ const availableTags = new Set(config.tags.map((tag) => tag.name));
896
+ for (const tag of tags) if (!availableTags.has(tag)) throw createNoTagsError(config.tags, tag);
897
+ }
898
+ function createNoTagsError(availableTags, tag, prefix = "tag") {
899
+ if (!availableTags.length) throw new Error(`The Vitest config does't define any "tags", cannot apply "${tag}" ${prefix} for this test. See: https://vitest.dev/guide/test-tags`);
900
+ throw new Error(`The ${prefix} "${tag}" is not defined in the configuration. Available tags are:\n${availableTags.map((t) => `- ${t.name}${t.description ? `: ${t.description}` : ""}`).join("\n")}`);
901
+ }
902
+ function createTagsFilter(tagsExpr, availableTags) {
903
+ const matchers = tagsExpr.map((expr) => parseTagsExpression(expr, availableTags));
904
+ return (testTags) => {
905
+ return matchers.every((matcher) => matcher(testTags));
906
+ };
907
+ }
908
+ function parseTagsExpression(expr, availableTags) {
909
+ const stream = new TokenStream(tokenize(expr), expr);
910
+ const ast = parseOrExpression(stream, availableTags);
911
+ if (stream.peek().type !== "EOF") throw new Error(`Invalid tags expression: unexpected "${formatToken(stream.peek())}" in "${expr}"`);
912
+ return (tags) => evaluateNode(ast, tags);
913
+ }
914
+ function formatToken(token) {
915
+ switch (token.type) {
916
+ case "TAG": return token.value;
917
+ default: return formatTokenType(token.type);
918
+ }
919
+ }
920
+ function tokenize(expr) {
921
+ const tokens = [];
922
+ let i = 0;
923
+ while (i < expr.length) {
924
+ if (expr[i] === " " || expr[i] === " ") {
925
+ i++;
926
+ continue;
927
+ }
928
+ if (expr[i] === "(") {
929
+ tokens.push({ type: "LPAREN" });
930
+ i++;
931
+ continue;
932
+ }
933
+ if (expr[i] === ")") {
934
+ tokens.push({ type: "RPAREN" });
935
+ i++;
936
+ continue;
937
+ }
938
+ if (expr[i] === "!") {
939
+ tokens.push({ type: "NOT" });
940
+ i++;
941
+ continue;
942
+ }
943
+ if (expr.slice(i, i + 2) === "&&") {
944
+ tokens.push({ type: "AND" });
945
+ i += 2;
946
+ continue;
947
+ }
948
+ if (expr.slice(i, i + 2) === "||") {
949
+ tokens.push({ type: "OR" });
950
+ i += 2;
951
+ continue;
952
+ }
953
+ if (/^and(?:\s|\)|$)/i.test(expr.slice(i))) {
954
+ tokens.push({ type: "AND" });
955
+ i += 3;
956
+ continue;
957
+ }
958
+ if (/^or(?:\s|\)|$)/i.test(expr.slice(i))) {
959
+ tokens.push({ type: "OR" });
960
+ i += 2;
961
+ continue;
962
+ }
963
+ if (/^not\s/i.test(expr.slice(i))) {
964
+ tokens.push({ type: "NOT" });
965
+ i += 3;
966
+ continue;
967
+ }
968
+ let tag = "";
969
+ while (i < expr.length && expr[i] !== " " && expr[i] !== " " && expr[i] !== "(" && expr[i] !== ")" && expr[i] !== "!" && expr[i] !== "&" && expr[i] !== "|") {
970
+ const remaining = expr.slice(i);
971
+ // Only treat and/or/not as operators if we're at the start of a tag (after whitespace)
972
+ // This allows tags like "demand", "editor", "cannot" to work correctly
973
+ if (tag === "" && (/^and(?:\s|\)|$)/i.test(remaining) || /^or(?:\s|\)|$)/i.test(remaining) || /^not\s/i.test(remaining))) break;
974
+ tag += expr[i];
975
+ i++;
976
+ }
977
+ if (tag) tokens.push({
978
+ type: "TAG",
979
+ value: tag
980
+ });
981
+ }
982
+ tokens.push({ type: "EOF" });
983
+ return tokens;
984
+ }
985
+ class TokenStream {
986
+ pos = 0;
987
+ constructor(tokens, expr) {
988
+ this.tokens = tokens;
989
+ this.expr = expr;
990
+ }
991
+ peek() {
992
+ return this.tokens[this.pos];
993
+ }
994
+ next() {
995
+ return this.tokens[this.pos++];
996
+ }
997
+ expect(type) {
998
+ const token = this.next();
999
+ if (token.type !== type) {
1000
+ if (type === "RPAREN" && token.type === "EOF") throw new Error(`Invalid tags expression: missing closing ")" in "${this.expr}"`);
1001
+ throw new Error(`Invalid tags expression: expected "${formatTokenType(type)}" but got "${formatToken(token)}" in "${this.expr}"`);
1002
+ }
1003
+ return token;
1004
+ }
1005
+ unexpectedToken() {
1006
+ const token = this.peek();
1007
+ if (token.type === "EOF") throw new Error(`Invalid tags expression: unexpected end of expression in "${this.expr}"`);
1008
+ throw new Error(`Invalid tags expression: unexpected "${formatToken(token)}" in "${this.expr}"`);
1009
+ }
1010
+ }
1011
+ function formatTokenType(type) {
1012
+ switch (type) {
1013
+ case "TAG": return "tag";
1014
+ case "AND": return "and";
1015
+ case "OR": return "or";
1016
+ case "NOT": return "not";
1017
+ case "LPAREN": return "(";
1018
+ case "RPAREN": return ")";
1019
+ case "EOF": return "end of expression";
1020
+ }
1021
+ }
1022
+ function parseOrExpression(stream, availableTags) {
1023
+ let left = parseAndExpression(stream, availableTags);
1024
+ while (stream.peek().type === "OR") {
1025
+ stream.next();
1026
+ const right = parseAndExpression(stream, availableTags);
1027
+ left = {
1028
+ type: "or",
1029
+ left,
1030
+ right
1031
+ };
1032
+ }
1033
+ return left;
1034
+ }
1035
+ function parseAndExpression(stream, availableTags) {
1036
+ let left = parseUnaryExpression(stream, availableTags);
1037
+ while (stream.peek().type === "AND") {
1038
+ stream.next();
1039
+ const right = parseUnaryExpression(stream, availableTags);
1040
+ left = {
1041
+ type: "and",
1042
+ left,
1043
+ right
1044
+ };
1045
+ }
1046
+ return left;
1047
+ }
1048
+ function parseUnaryExpression(stream, availableTags) {
1049
+ if (stream.peek().type === "NOT") {
1050
+ stream.next();
1051
+ return {
1052
+ type: "not",
1053
+ operand: parseUnaryExpression(stream, availableTags)
1054
+ };
1055
+ }
1056
+ return parsePrimaryExpression(stream, availableTags);
1057
+ }
1058
+ function parsePrimaryExpression(stream, availableTags) {
1059
+ const token = stream.peek();
1060
+ if (token.type === "LPAREN") {
1061
+ stream.next();
1062
+ const expr = parseOrExpression(stream, availableTags);
1063
+ stream.expect("RPAREN");
1064
+ return expr;
1065
+ }
1066
+ if (token.type === "TAG") {
1067
+ stream.next();
1068
+ const tagValue = token.value;
1069
+ return {
1070
+ type: "tag",
1071
+ value: tagValue,
1072
+ pattern: resolveTagPattern(tagValue, availableTags)
1073
+ };
1074
+ }
1075
+ stream.unexpectedToken();
1076
+ }
1077
+ function createWildcardRegex(pattern) {
1078
+ return new RegExp(`^${pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
1079
+ }
1080
+ function resolveTagPattern(tagPattern, availableTags) {
1081
+ if (tagPattern.includes("*")) {
1082
+ const regex = createWildcardRegex(tagPattern);
1083
+ if (!availableTags.some((tag) => regex.test(tag.name))) throw createNoTagsError(availableTags, tagPattern, "tag pattern");
1084
+ return regex;
1085
+ }
1086
+ if (!availableTags.length || !availableTags.some((tag) => tag.name === tagPattern)) throw createNoTagsError(availableTags, tagPattern, "tag pattern");
1087
+ return null;
1088
+ }
1089
+ function evaluateNode(node, tags) {
1090
+ switch (node.type) {
1091
+ case "tag":
1092
+ if (node.pattern) return tags.some((tag) => node.pattern.test(tag));
1093
+ return tags.includes(node.value);
1094
+ case "not": return !evaluateNode(node.operand, tags);
1095
+ case "and": return evaluateNode(node.left, tags) && evaluateNode(node.right, tags);
1096
+ case "or": return evaluateNode(node.left, tags) || evaluateNode(node.right, tags);
1097
+ }
1098
+ }
1099
+
1100
+ /**
1101
+ * Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
1102
+ * Suites can contain both tests and other suites, enabling complex test structures.
1103
+ *
1104
+ * @param {string} name - The name of the suite, used for identification and reporting.
1105
+ * @param {Function} fn - A function that defines the tests and suites within this suite.
1106
+ * @example
1107
+ * ```ts
1108
+ * // Define a suite with two tests
1109
+ * suite('Math operations', () => {
1110
+ * test('should add two numbers', () => {
1111
+ * expect(add(1, 2)).toBe(3);
1112
+ * });
1113
+ *
1114
+ * test('should subtract two numbers', () => {
1115
+ * expect(subtract(5, 2)).toBe(3);
1116
+ * });
1117
+ * });
1118
+ * ```
1119
+ * @example
1120
+ * ```ts
1121
+ * // Define nested suites
1122
+ * suite('String operations', () => {
1123
+ * suite('Trimming', () => {
1124
+ * test('should trim whitespace from start and end', () => {
1125
+ * expect(' hello '.trim()).toBe('hello');
1126
+ * });
1127
+ * });
1128
+ *
1129
+ * suite('Concatenation', () => {
1130
+ * test('should concatenate two strings', () => {
1131
+ * expect('hello' + ' ' + 'world').toBe('hello world');
1132
+ * });
1133
+ * });
1134
+ * });
1135
+ * ```
1136
+ */
1137
+ const suite = createSuite();
1138
+ /**
1139
+ * Defines a test case with a given name and test function. The test function can optionally be configured with test options.
1140
+ *
1141
+ * @param {string | Function} name - The name of the test or a function that will be used as a test name.
1142
+ * @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
1143
+ * @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
1144
+ * @throws {Error} If called inside another test function.
1145
+ * @example
1146
+ * ```ts
1147
+ * // Define a simple test
1148
+ * test('should add two numbers', () => {
1149
+ * expect(add(1, 2)).toBe(3);
1150
+ * });
1151
+ * ```
1152
+ * @example
1153
+ * ```ts
1154
+ * // Define a test with options
1155
+ * test('should subtract two numbers', { retry: 3 }, () => {
1156
+ * expect(subtract(5, 2)).toBe(3);
1157
+ * });
1158
+ * ```
1159
+ */
1160
+ const test = createTest(function(name, optionsOrFn, optionsOrTest) {
1161
+ if (getCurrentTest()) 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.");
1162
+ getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
1163
+ });
1164
+ /**
1165
+ * Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
1166
+ * Suites can contain both tests and other suites, enabling complex test structures.
1167
+ *
1168
+ * @param {string} name - The name of the suite, used for identification and reporting.
1169
+ * @param {Function} fn - A function that defines the tests and suites within this suite.
1170
+ * @example
1171
+ * ```ts
1172
+ * // Define a suite with two tests
1173
+ * describe('Math operations', () => {
1174
+ * test('should add two numbers', () => {
1175
+ * expect(add(1, 2)).toBe(3);
1176
+ * });
1177
+ *
1178
+ * test('should subtract two numbers', () => {
1179
+ * expect(subtract(5, 2)).toBe(3);
1180
+ * });
1181
+ * });
1182
+ * ```
1183
+ * @example
1184
+ * ```ts
1185
+ * // Define nested suites
1186
+ * describe('String operations', () => {
1187
+ * describe('Trimming', () => {
1188
+ * test('should trim whitespace from start and end', () => {
1189
+ * expect(' hello '.trim()).toBe('hello');
1190
+ * });
1191
+ * });
1192
+ *
1193
+ * describe('Concatenation', () => {
1194
+ * test('should concatenate two strings', () => {
1195
+ * expect('hello' + ' ' + 'world').toBe('hello world');
1196
+ * });
1197
+ * });
1198
+ * });
1199
+ * ```
1200
+ */
1201
+ const describe = suite;
1202
+ /**
1203
+ * Defines a test case with a given name and test function. The test function can optionally be configured with test options.
1204
+ *
1205
+ * @param {string | Function} name - The name of the test or a function that will be used as a test name.
1206
+ * @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
1207
+ * @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
1208
+ * @throws {Error} If called inside another test function.
1209
+ * @example
1210
+ * ```ts
1211
+ * // Define a simple test
1212
+ * it('adds two numbers', () => {
1213
+ * expect(add(1, 2)).toBe(3);
1214
+ * });
1215
+ * ```
1216
+ * @example
1217
+ * ```ts
1218
+ * // Define a test with options
1219
+ * it('subtracts two numbers', { retry: 3 }, () => {
1220
+ * expect(subtract(5, 2)).toBe(3);
1221
+ * });
1222
+ * ```
1223
+ */
1224
+ const it = test;
1225
+ let runner;
1226
+ let defaultSuite;
1227
+ let currentTestFilepath;
1228
+ function assert(condition, message) {
1229
+ if (!condition) throw new Error(`Vitest failed to find ${message}. One of the following is possible:
1230
+ - "vitest" is imported directly without running "vitest" command
1231
+ - "vitest" is imported inside "globalSetup" (to fix this, use "setupFiles" instead, because "globalSetup" runs in a different context)
1232
+ - "vitest" is imported inside Vite / Vitest config file
1233
+ - Otherwise, it might be a Vitest bug. Please report it to https://github.com/vitest-dev/vitest/issues
1234
+ `);
1235
+ }
1236
+ function getDefaultSuite() {
1237
+ assert(defaultSuite, "the default suite");
1238
+ return defaultSuite;
1239
+ }
1240
+ function getRunner() {
1241
+ assert(runner, "the runner");
1242
+ return runner;
1243
+ }
1244
+ function createDefaultSuite(runner) {
1245
+ const config = runner.config.sequence;
1246
+ const options = {};
1247
+ if (config.concurrent != null) options.concurrent = config.concurrent;
1248
+ const collector = suite("", options, () => {});
1249
+ // no parent suite for top-level tests
1250
+ delete collector.suite;
1251
+ return collector;
1252
+ }
1253
+ function clearCollectorContext(file, currentRunner) {
1254
+ currentTestFilepath = file.filepath;
1255
+ runner = currentRunner;
1256
+ if (!defaultSuite) defaultSuite = createDefaultSuite(currentRunner);
1257
+ defaultSuite.file = file;
1258
+ collectorContext.tasks.length = 0;
1259
+ defaultSuite.clear();
1260
+ collectorContext.currentSuite = defaultSuite;
1261
+ }
1262
+ function getCurrentSuite() {
1263
+ const currentSuite = collectorContext.currentSuite || defaultSuite;
1264
+ assert(currentSuite, "the current suite");
1265
+ return currentSuite;
1266
+ }
1267
+ function createSuiteHooks() {
1268
+ return {
1269
+ beforeAll: [],
1270
+ afterAll: [],
1271
+ beforeEach: [],
1272
+ afterEach: [],
1273
+ aroundEach: [],
1274
+ aroundAll: []
1275
+ };
1276
+ }
1277
+ const POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
1278
+ function parseArguments(optionsOrFn, timeoutOrTest) {
1279
+ if (timeoutOrTest != null && typeof timeoutOrTest === "object") throw new TypeError(`Signature "test(name, fn, { ... })" was deprecated in Vitest 3 and removed in Vitest 4. Please, provide options as a second argument instead.`);
1280
+ let options = {};
1281
+ let fn;
1282
+ // it('', () => {}, 1000)
1283
+ if (typeof timeoutOrTest === "number") options = { timeout: timeoutOrTest };
1284
+ else if (typeof optionsOrFn === "object") options = optionsOrFn;
1285
+ if (typeof optionsOrFn === "function") {
1286
+ if (typeof timeoutOrTest === "function") throw new TypeError("Cannot use two functions as arguments. Please use the second argument for options.");
1287
+ fn = optionsOrFn;
1288
+ } else if (typeof timeoutOrTest === "function") fn = timeoutOrTest;
1289
+ return {
1290
+ options,
1291
+ handler: fn
1292
+ };
1293
+ }
1294
+ // implementations
1295
+ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
1296
+ const tasks = [];
1297
+ let suite;
1298
+ initSuite(true);
1299
+ const task = function(name = "", options = {}) {
1300
+ const currentSuite = collectorContext.currentSuite?.suite;
1301
+ const testTags = unique([...(currentSuite ?? collectorContext.currentSuite?.file)?.tags || [], ...toArray(options.tags)]);
1302
+ const tagsOptions = testTags.map((tag) => {
1303
+ const tagDefinition = runner.config.tags?.find((t) => t.name === tag);
1304
+ if (!tagDefinition && runner.config.strictTags) throw createNoTagsError(runner.config.tags, tag);
1305
+ return tagDefinition;
1306
+ }).filter((r) => r != null).sort((tag1, tag2) => (tag2.priority ?? POSITIVE_INFINITY) - (tag1.priority ?? POSITIVE_INFINITY)).reduce((acc, tag) => {
1307
+ const { name, description, priority, meta, ...options } = tag;
1308
+ Object.assign(acc, options);
1309
+ if (meta) acc.meta = Object.assign(acc.meta ?? Object.create(null), meta);
1310
+ return acc;
1311
+ }, {});
1312
+ const testOwnMeta = options.meta;
1313
+ options = {
1314
+ ...collectorContext.currentSuite?.options,
1315
+ ...tagsOptions,
1316
+ ...options
1317
+ };
1318
+ const timeout = options.timeout ?? runner.config.testTimeout;
1319
+ // TODO: should this be `parentTask.meta`?
1320
+ // currently we don't inherit
1321
+ // file.meta -> task.meta
1322
+ // file.meta -> suite.meta (see initSuite)
1323
+ // but we do inherit
1324
+ // suite.meta -> task.meta
1325
+ // suite.meta -> suite.meta
1326
+ // and also
1327
+ // file.tags -> task.tags
1328
+ // file.tags -> suite.tags
1329
+ // suite.tags -> suite.tags
1330
+ // suite.tags -> task.tags
1331
+ const parentMeta = currentSuite?.meta;
1332
+ const tagMeta = tagsOptions.meta;
1333
+ const testMeta = Object.create(null);
1334
+ if (tagMeta) Object.assign(testMeta, tagMeta);
1335
+ if (parentMeta) Object.assign(testMeta, parentMeta);
1336
+ if (testOwnMeta) Object.assign(testMeta, testOwnMeta);
1337
+ const task = {
1338
+ id: "",
1339
+ name,
1340
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1341
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
1342
+ suite: currentSuite,
1343
+ each: options.each,
1344
+ fails: options.fails,
1345
+ context: void 0,
1346
+ type: "test",
1347
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
1348
+ timeout,
1349
+ retry: options.retry ?? runner.config.retry,
1350
+ repeats: options.repeats ?? runner.config.repeats,
1351
+ mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
1352
+ meta: testMeta,
1353
+ annotations: [],
1354
+ artifacts: [],
1355
+ benchmarks: [],
1356
+ tags: testTags
1357
+ };
1358
+ const handler = options.handler;
1359
+ if (task.mode === "run" && !handler) task.mode = "todo";
1360
+ if (options.concurrent ?? runner.config.sequence.concurrent) task.concurrent = true;
1361
+ task.shuffle = suiteOptions?.shuffle;
1362
+ const context = createTestContext(task, runner);
1363
+ // create test context
1364
+ Object.defineProperty(task, "context", {
1365
+ value: context,
1366
+ enumerable: false
1367
+ });
1368
+ setTestFixture(context, options.fixtures ?? new TestFixtures());
1369
+ const limit = Error.stackTraceLimit;
1370
+ Error.stackTraceLimit = 10;
1371
+ const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
1372
+ Error.stackTraceLimit = limit;
1373
+ if (handler) setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, { context }), task), task.context.signal), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
1374
+ if (runner.config.includeTaskLocation) {
1375
+ const error = stackTraceError.stack;
1376
+ const stack = findTestFileStackTrace(currentTestFilepath, error);
1377
+ if (stack) task.location = {
1378
+ line: stack.line,
1379
+ column: stack.column
1380
+ };
1381
+ }
1382
+ tasks.push(task);
1383
+ return task;
1384
+ };
1385
+ const test = createTest(function(name, optionsOrFn, timeoutOrTest) {
1386
+ const { options, handler } = parseArguments(optionsOrFn, timeoutOrTest);
1387
+ const concurrent = this.concurrent ?? options?.concurrent;
1388
+ if (concurrent != null) options.concurrent = concurrent;
1389
+ const test = task(formatName(name), {
1390
+ ...this,
1391
+ ...options,
1392
+ handler
1393
+ });
1394
+ test.type = "test";
1395
+ });
1396
+ const collector = {
1397
+ type: "collector",
1398
+ name,
1399
+ mode,
1400
+ suite,
1401
+ options: suiteOptions,
1402
+ test,
1403
+ file: suite.file,
1404
+ tasks,
1405
+ collect,
1406
+ task,
1407
+ clear,
1408
+ on: addHook
1409
+ };
1410
+ function addHook(name, ...fn) {
1411
+ getHooks(suite)[name].push(...fn);
1412
+ }
1413
+ function initSuite(includeLocation) {
1414
+ if (typeof suiteOptions === "number") suiteOptions = { timeout: suiteOptions };
1415
+ const currentSuite = collectorContext.currentSuite?.suite;
1416
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
1417
+ const suiteTags = toArray(suiteOptions?.tags);
1418
+ validateTags(runner.config, suiteTags);
1419
+ suite = {
1420
+ id: "",
1421
+ type: "suite",
1422
+ name,
1423
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1424
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
1425
+ suite: currentSuite,
1426
+ mode,
1427
+ each,
1428
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
1429
+ shuffle: suiteOptions?.shuffle,
1430
+ tasks: [],
1431
+ meta: suiteOptions?.meta ?? Object.create(null),
1432
+ concurrent: suiteOptions?.concurrent,
1433
+ tags: unique([...parentTask?.tags || [], ...suiteTags])
1434
+ };
1435
+ if (runner && includeLocation && runner.config.includeTaskLocation) {
1436
+ const limit = Error.stackTraceLimit;
1437
+ Error.stackTraceLimit = 15;
1438
+ const error = (/* @__PURE__ */ new Error("stacktrace")).stack;
1439
+ Error.stackTraceLimit = limit;
1440
+ const stack = findTestFileStackTrace(currentTestFilepath, error);
1441
+ if (stack) suite.location = {
1442
+ line: stack.line,
1443
+ column: stack.column
1444
+ };
1445
+ }
1446
+ setHooks(suite, createSuiteHooks());
1447
+ }
1448
+ function clear() {
1449
+ tasks.length = 0;
1450
+ initSuite(false);
1451
+ }
1452
+ async function collect(file) {
1453
+ if (!file) throw new TypeError("File is required to collect tasks.");
1454
+ if (factory) await runWithSuite(collector, () => factory(test));
1455
+ const allChildren = [];
1456
+ for (const i of tasks) allChildren.push(i.type === "collector" ? await i.collect(file) : i);
1457
+ suite.tasks = allChildren;
1458
+ return suite;
1459
+ }
1460
+ collectTask(collector);
1461
+ return collector;
1462
+ }
1463
+ function withAwaitAsyncAssertions(fn, task) {
1464
+ return (async (...args) => {
1465
+ const fnResult = await fn(...args);
1466
+ // some async expect will be added to this array, in case user forget to await them
1467
+ if (task.promises) {
1468
+ const errors = (await Promise.allSettled(task.promises)).map((r) => r.status === "rejected" ? r.reason : void 0).filter(Boolean);
1469
+ if (errors.length) throw errors;
1470
+ }
1471
+ return fnResult;
1472
+ });
1473
+ }
1474
+ function createSuite() {
1475
+ function suiteFn(name, factoryOrOptions, optionsOrFactory) {
1476
+ if (getCurrentTest()) throw new Error("Calling the suite function inside test function is not allowed. It can be only called at the top level or inside another suite function.");
1477
+ const currentSuite = collectorContext.currentSuite || defaultSuite;
1478
+ let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
1479
+ const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
1480
+ // inherit options from current suite
1481
+ options = {
1482
+ ...parentOptions,
1483
+ ...options
1484
+ };
1485
+ const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
1486
+ if (shuffle != null) options.shuffle = shuffle;
1487
+ let mode = this.only ?? options.only ? "only" : this.skip ?? options.skip ? "skip" : this.todo ?? options.todo ? "todo" : "run";
1488
+ // passed as test(name), assume it's a "todo"
1489
+ if (mode === "run" && !factory) mode = "todo";
1490
+ const concurrent = this.concurrent ?? options.concurrent;
1491
+ if (concurrent != null) options.concurrent = concurrent;
1492
+ if (parentMeta) options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
1493
+ return createSuiteCollector(formatName(name), factory, mode, this.each, options);
1494
+ }
1495
+ suiteFn.each = function(cases, ...args) {
1496
+ const context = getChainableContext(this);
1497
+ const suite = context.withContext();
1498
+ context.setContext("each", true);
1499
+ if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
1500
+ return (name, optionsOrFn, fnOrOptions) => {
1501
+ const _name = formatName(name);
1502
+ const arrayOnlyCases = cases.every(Array.isArray);
1503
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
1504
+ const fnFirst = typeof optionsOrFn === "function";
1505
+ cases.forEach((i, idx) => {
1506
+ const items = Array.isArray(i) ? i : [i];
1507
+ if (fnFirst) if (arrayOnlyCases) suite(formatTitle(_name, items, idx), handler ? () => handler(...items) : void 0, options.timeout);
1508
+ else suite(formatTitle(_name, items, idx), handler ? () => handler(i) : void 0, options.timeout);
1509
+ else if (arrayOnlyCases) suite(formatTitle(_name, items, idx), options, handler ? () => handler(...items) : void 0);
1510
+ else suite(formatTitle(_name, items, idx), options, handler ? () => handler(i) : void 0);
1511
+ });
1512
+ context.setContext("each", void 0);
1513
+ };
1514
+ };
1515
+ suiteFn.for = function(cases, ...args) {
1516
+ const suite = getChainableContext(this).withContext();
1517
+ if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
1518
+ return (name, optionsOrFn, fnOrOptions) => {
1519
+ const name_ = formatName(name);
1520
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
1521
+ cases.forEach((item, idx) => {
1522
+ suite(formatTitle(name_, toArray(item), idx), options, handler ? () => handler(item) : void 0);
1523
+ });
1524
+ };
1525
+ };
1526
+ suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
1527
+ suiteFn.runIf = (condition) => condition ? suite : suite.skip;
1528
+ return createChainable([
1529
+ "concurrent",
1530
+ "shuffle",
1531
+ "skip",
1532
+ "only",
1533
+ "todo"
1534
+ ], suiteFn);
1535
+ }
1536
+ function createTaskCollector(fn) {
1537
+ const taskFn = fn;
1538
+ taskFn.each = function(cases, ...args) {
1539
+ const context = getChainableContext(this);
1540
+ const test = context.withContext();
1541
+ context.setContext("each", true);
1542
+ if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
1543
+ return (name, optionsOrFn, fnOrOptions) => {
1544
+ const _name = formatName(name);
1545
+ const arrayOnlyCases = cases.every(Array.isArray);
1546
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
1547
+ const fnFirst = typeof optionsOrFn === "function";
1548
+ cases.forEach((i, idx) => {
1549
+ const items = Array.isArray(i) ? i : [i];
1550
+ if (fnFirst) if (arrayOnlyCases) test(formatTitle(_name, items, idx), handler ? () => handler(...items) : void 0, options.timeout);
1551
+ else test(formatTitle(_name, items, idx), handler ? () => handler(i) : void 0, options.timeout);
1552
+ else if (arrayOnlyCases) test(formatTitle(_name, items, idx), options, handler ? () => handler(...items) : void 0);
1553
+ else test(formatTitle(_name, items, idx), options, handler ? () => handler(i) : void 0);
1554
+ });
1555
+ context.setContext("each", void 0);
1556
+ };
1557
+ };
1558
+ taskFn.for = function(cases, ...args) {
1559
+ const test = getChainableContext(this).withContext();
1560
+ if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
1561
+ return (name, optionsOrFn, fnOrOptions) => {
1562
+ const _name = formatName(name);
1563
+ const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
1564
+ cases.forEach((item, idx) => {
1565
+ // monkey-patch handler to allow parsing fixture
1566
+ const handlerWrapper = handler ? (ctx) => handler(item, ctx) : void 0;
1567
+ if (handlerWrapper) configureProps(handlerWrapper, {
1568
+ index: 1,
1569
+ original: handler
1570
+ });
1571
+ test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
1572
+ });
1573
+ };
1574
+ };
1575
+ taskFn.skipIf = function(condition) {
1576
+ return condition ? this.skip : this;
1577
+ };
1578
+ taskFn.runIf = function(condition) {
1579
+ return condition ? this : this.skip;
1580
+ };
1581
+ /**
1582
+ * Parse builder pattern arguments into a fixtures object.
1583
+ * Handles both builder pattern (name, options?, value) and object syntax.
1584
+ */
1585
+ function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
1586
+ // Object syntax: just return as-is
1587
+ if (typeof fixturesOrName !== "string") return fixturesOrName;
1588
+ const fixtureName = fixturesOrName;
1589
+ let fixtureOptions;
1590
+ let fixtureValue;
1591
+ if (maybeFn !== void 0) {
1592
+ // (name, options, value) or (name, options, fn)
1593
+ fixtureOptions = optionsOrFn;
1594
+ fixtureValue = maybeFn;
1595
+ } else if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && TestFixtures.isFixtureOptions(optionsOrFn)) {
1596
+ // (name, options) with no value - treat as empty object fixture
1597
+ fixtureOptions = optionsOrFn;
1598
+ fixtureValue = {};
1599
+ } else {
1600
+ // (name, value) or (name, fn)
1601
+ fixtureOptions = void 0;
1602
+ fixtureValue = optionsOrFn;
1603
+ }
1604
+ // Function value: wrap with onCleanup pattern
1605
+ if (typeof fixtureValue === "function") {
1606
+ const builderFn = fixtureValue;
1607
+ // Wrap builder pattern function (returns value) to use() pattern
1608
+ const fixture = async (ctx, use) => {
1609
+ let cleanup;
1610
+ const onCleanup = (fn) => {
1611
+ if (cleanup !== void 0) throw new Error("onCleanup can only be called once per fixture. Define separate fixtures if you need multiple cleanup functions.");
1612
+ cleanup = fn;
1613
+ };
1614
+ await use(await builderFn(ctx, { onCleanup }));
1615
+ if (cleanup) await cleanup();
1616
+ };
1617
+ configureProps(fixture, { original: builderFn });
1618
+ if (fixtureOptions) return { [fixtureName]: [fixture, fixtureOptions] };
1619
+ return { [fixtureName]: fixture };
1620
+ }
1621
+ // Non-function value: use directly
1622
+ if (fixtureOptions) return { [fixtureName]: [fixtureValue, fixtureOptions] };
1623
+ return { [fixtureName]: fixtureValue };
1624
+ }
1625
+ taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
1626
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1627
+ getChainableContext(this).getFixtures().override(runner, userFixtures);
1628
+ return this;
1629
+ };
1630
+ taskFn.scoped = function(fixtures) {
1631
+ console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
1632
+ return this.override(fixtures);
1633
+ };
1634
+ taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
1635
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1636
+ const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
1637
+ const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
1638
+ fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
1639
+ });
1640
+ getChainableContext(_test).mergeContext({ fixtures });
1641
+ return _test;
1642
+ };
1643
+ taskFn.describe = suite;
1644
+ taskFn.suite = suite;
1645
+ taskFn.beforeEach = beforeEach;
1646
+ taskFn.afterEach = afterEach;
1647
+ taskFn.beforeAll = beforeAll;
1648
+ taskFn.afterAll = afterAll;
1649
+ taskFn.aroundEach = aroundEach;
1650
+ taskFn.aroundAll = aroundAll;
1651
+ return createChainable([
1652
+ "concurrent",
1653
+ "skip",
1654
+ "only",
1655
+ "todo",
1656
+ "fails"
1657
+ ], taskFn, { fixtures: new TestFixtures() });
1658
+ }
1659
+ function createTest(fn) {
1660
+ return createTaskCollector(fn);
1661
+ }
1662
+ function formatName(name) {
1663
+ return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
1664
+ }
1665
+ function formatTitle(template, items, idx) {
1666
+ if (template.includes("%#") || template.includes("%$"))
1667
+ // '%#' match index of the test case
1668
+ template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/%\$/g, `${idx + 1}`).replace(/__vitest_escaped_%__/g, "%%");
1669
+ const count = template.split("%").length - 1;
1670
+ if (template.includes("%f")) (template.match(/%f/g) || []).forEach((_, i) => {
1671
+ if (isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
1672
+ // Replace the i-th occurrence of '%f' with '-%f'
1673
+ let occurrence = 0;
1674
+ template = template.replace(/%f/g, (match) => {
1675
+ occurrence++;
1676
+ return occurrence === i + 1 ? "-%f" : match;
1677
+ });
1678
+ }
1679
+ });
1680
+ const inspectOptions = { truncate: runner.config.taskTitleValueFormatTruncate };
1681
+ const isObjectItem = isObject(items[0]);
1682
+ function formatAttribute(s) {
1683
+ return s.replace(/\$([$\w.]+)/g, (_, key) => {
1684
+ const isArrayKey = /^\d+$/.test(key);
1685
+ if (!isObjectItem && !isArrayKey) return `$${key}`;
1686
+ const arrayElement = isArrayKey ? objectAttr(items, key) : void 0;
1687
+ const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
1688
+ // print string without quotes
1689
+ if (typeof value === "string") return truncateString(value, inspectOptions.truncate);
1690
+ return inspect(value, inspectOptions);
1691
+ });
1692
+ }
1693
+ let output = "";
1694
+ let i = 0;
1695
+ handleRegexMatch(
1696
+ template,
1697
+ formatRegExp,
1698
+ // format "%"
1699
+ (match) => {
1700
+ if (i < count) output += format([match[0], items[i++]], inspectOptions);
1701
+ else output += match[0];
1702
+ },
1703
+ // format "$"
1704
+ (nonMatch) => {
1705
+ output += formatAttribute(nonMatch);
1706
+ }
1707
+ );
1708
+ return output;
1709
+ }
1710
+ // based on https://github.com/unocss/unocss/blob/2e74b31625bbe3b9c8351570749aa2d3f799d919/packages/autocomplete/src/parse.ts#L11
1711
+ function handleRegexMatch(input, regex, onMatch, onNonMatch) {
1712
+ let lastIndex = 0;
1713
+ for (const m of input.matchAll(regex)) {
1714
+ if (lastIndex < m.index) onNonMatch(input.slice(lastIndex, m.index));
1715
+ onMatch(m);
1716
+ lastIndex = m.index + m[0].length;
1717
+ }
1718
+ if (lastIndex < input.length) onNonMatch(input.slice(lastIndex));
1719
+ }
1720
+ function formatTemplateString(cases, args) {
1721
+ const header = cases.join("").trim().replace(/ /g, "").split("\n").map((i) => i.split("|"))[0];
1722
+ const res = [];
1723
+ for (let i = 0; i < Math.floor(args.length / header.length); i++) {
1724
+ const oneCase = {};
1725
+ for (let j = 0; j < header.length; j++) oneCase[header[j]] = args[i * header.length + j];
1726
+ res.push(oneCase);
1727
+ }
1728
+ return res;
1729
+ }
1730
+
1731
+ const now$2 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1732
+ const collectorContext = {
1733
+ tasks: [],
1734
+ currentSuite: null
1735
+ };
1736
+ function collectTask(task) {
1737
+ collectorContext.currentSuite?.tasks.push(task);
1738
+ }
1739
+ async function runWithSuite(suite, fn) {
1740
+ const prev = collectorContext.currentSuite;
1741
+ collectorContext.currentSuite = suite;
1742
+ await fn();
1743
+ collectorContext.currentSuite = prev;
1744
+ }
1745
+ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
1746
+ if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) return fn;
1747
+ const { setTimeout, clearTimeout } = getSafeTimers();
1748
+ // this function name is used to filter error in test/e2e/test/fails.test.ts
1749
+ return (function runWithTimeout(...args) {
1750
+ const startTime = now$2();
1751
+ const runner = getRunner();
1752
+ runner._currentTaskStartTime = startTime;
1753
+ runner._currentTaskTimeout = timeout;
1754
+ return new Promise((resolve_, reject_) => {
1755
+ const timer = setTimeout(() => {
1756
+ clearTimeout(timer);
1757
+ rejectTimeoutError();
1758
+ }, timeout);
1759
+ // `unref` might not exist in browser
1760
+ timer.unref?.();
1761
+ function rejectTimeoutError() {
1762
+ const error = makeTimeoutError(isHook, timeout, stackTraceError);
1763
+ onTimeout?.(args, error);
1764
+ reject_(error);
1765
+ }
1766
+ function resolve(result) {
1767
+ runner._currentTaskStartTime = void 0;
1768
+ runner._currentTaskTimeout = void 0;
1769
+ clearTimeout(timer);
1770
+ // if test/hook took too long in microtask, setTimeout won't be triggered,
1771
+ // but we still need to fail the test, see
1772
+ // https://github.com/vitest-dev/vitest/issues/2920
1773
+ if (now$2() - startTime >= timeout) {
1774
+ rejectTimeoutError();
1775
+ return;
1776
+ }
1777
+ resolve_(result);
1778
+ }
1779
+ function reject(error) {
1780
+ runner._currentTaskStartTime = void 0;
1781
+ runner._currentTaskTimeout = void 0;
1782
+ clearTimeout(timer);
1783
+ reject_(error);
1784
+ }
1785
+ // sync test/hook will be caught by try/catch
1786
+ try {
1787
+ const result = fn(...args);
1788
+ // the result is a thenable, we don't wrap this in Promise.resolve
1789
+ // to avoid creating new promises
1790
+ if (typeof result === "object" && result != null && typeof result.then === "function") result.then(resolve, reject);
1791
+ else resolve(result);
1792
+ }
1793
+ // user sync test/hook throws an error
1794
+ catch (error) {
1795
+ reject(error);
1796
+ }
1797
+ });
1798
+ });
1799
+ }
1800
+ function withCancel(fn, signal) {
1801
+ return (function runWithCancel(...args) {
1802
+ return new Promise((resolve, reject) => {
1803
+ const onAbort = () => reject(signal.reason);
1804
+ signal.addEventListener("abort", onAbort, { once: true });
1805
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
1806
+ try {
1807
+ const result = fn(...args);
1808
+ if (typeof result === "object" && result != null && typeof result.then === "function") result.then((value) => {
1809
+ cleanup();
1810
+ resolve(value);
1811
+ }, (error) => {
1812
+ cleanup();
1813
+ reject(error);
1814
+ });
1815
+ else {
1816
+ cleanup();
1817
+ resolve(result);
1818
+ }
1819
+ } catch (error) {
1820
+ cleanup();
1821
+ reject(error);
1822
+ }
1823
+ });
1824
+ });
1825
+ }
1826
+ const abortControllers = /* @__PURE__ */ new WeakMap();
1827
+ function abortIfTimeout([context], error) {
1828
+ if (context) abortContextSignal(context, error);
1829
+ }
1830
+ function abortContextSignal(context, error) {
1831
+ abortControllers.get(context)?.abort(error);
1832
+ }
1833
+ function createTestContext(test, runner) {
1834
+ const context = function() {
1835
+ throw new Error("done() callback is deprecated, use promise instead");
1836
+ };
1837
+ let abortController = abortControllers.get(context);
1838
+ if (!abortController) {
1839
+ abortController = new AbortController();
1840
+ abortControllers.set(context, abortController);
1841
+ }
1842
+ context.signal = abortController.signal;
1843
+ context.task = test;
1844
+ context.skip = (condition, note) => {
1845
+ if (condition === false)
1846
+ // do nothing
1847
+ return;
1848
+ test.result ??= { state: "skip" };
1849
+ test.result.pending = true;
1850
+ throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
1851
+ };
1852
+ context.annotate = ((message, type, attachment) => {
1853
+ if (test.result && test.result.state !== "run") throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`);
1854
+ const annotation = {
1855
+ message,
1856
+ type: typeof type === "object" || type === void 0 ? "notice" : type
1857
+ };
1858
+ const annotationAttachment = typeof type === "object" ? type : attachment;
1859
+ if (annotationAttachment) {
1860
+ annotation.attachment = annotationAttachment;
1861
+ manageArtifactAttachment(annotation.attachment);
1862
+ }
1863
+ return recordAsyncOperation(test, recordArtifact(test, {
1864
+ type: "internal:annotation",
1865
+ annotation
1866
+ }).then(async ({ annotation }) => {
1867
+ if (!runner.onTestAnnotate) throw new Error(`Test runner doesn't support test annotations.`);
1868
+ await finishSendTasksUpdate(runner);
1869
+ const resolvedAnnotation = await runner.onTestAnnotate(test, annotation);
1870
+ test.annotations.push(resolvedAnnotation);
1871
+ return resolvedAnnotation;
1872
+ }));
1873
+ });
1874
+ context.onTestFailed = (handler, timeout) => {
1875
+ test.onFailed ||= [];
1876
+ test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1877
+ };
1878
+ context.onTestFinished = (handler, timeout) => {
1879
+ test.onFinished ||= [];
1880
+ test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1881
+ };
1882
+ return runner.extendTaskContext?.(context) || context;
1883
+ }
1884
+ function makeTimeoutError(isHook, timeout, stackTraceError) {
1885
+ const message = `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
1886
+ const error = new Error(message);
1887
+ if (stackTraceError?.stack) error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
1888
+ return error;
1889
+ }
1890
+
1891
+ async function runSetupFiles(config, files, runner) {
1892
+ if (config.sequence.setupFiles === "parallel") await Promise.all(files.map(async (fsPath) => {
1893
+ await runner.importFile(fsPath, "setup");
1894
+ }));
1895
+ else for (const fsPath of files) await runner.importFile(fsPath, "setup");
1896
+ }
1897
+
1898
+ const now$1 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1899
+ async function collectTests(specs, runner) {
1900
+ const files = [];
1901
+ const config = runner.config;
1902
+ const $ = runner.trace;
1903
+ let defaultTagsFilter;
1904
+ for (const spec of specs) {
1905
+ const filepath = typeof spec === "string" ? spec : spec.filepath;
1906
+ await $("collect_spec", { "code.file.path": filepath }, async () => {
1907
+ runner._currentSpecification = typeof spec === "string" ? { filepath: spec } : spec;
1908
+ const testLocations = typeof spec === "string" ? void 0 : spec.testLocations;
1909
+ const testNamePattern = typeof spec === "string" ? void 0 : spec.testNamePattern;
1910
+ const testIds = typeof spec === "string" ? void 0 : spec.testIds;
1911
+ const testTagsFilter = typeof spec === "object" && spec.testTagsFilter ? createTagsFilter(spec.testTagsFilter, config.tags) : void 0;
1912
+ const fileTags = typeof spec === "string" ? [] : spec.fileTags || [];
1913
+ const file = createFileTask(filepath, config.root, config.name, runner.pool, runner.viteEnvironment, { __vitest_label__: config.mergeReportsLabel });
1914
+ file.tags = fileTags;
1915
+ file.shuffle = config.sequence.shuffle;
1916
+ try {
1917
+ validateTags(runner.config, fileTags);
1918
+ runner.onCollectStart?.(file);
1919
+ clearCollectorContext(file, runner);
1920
+ const setupFiles = toArray(config.setupFiles);
1921
+ if (setupFiles.length) {
1922
+ const setupStart = now$1();
1923
+ await runSetupFiles(config, setupFiles, runner);
1924
+ file.setupDuration = now$1() - setupStart;
1925
+ } else file.setupDuration = 0;
1926
+ const collectStart = now$1();
1927
+ await runner.importFile(filepath, "collect");
1928
+ const durations = runner.getImportDurations?.();
1929
+ if (durations) file.importDurations = durations;
1930
+ const defaultTasks = await getDefaultSuite().collect(file);
1931
+ const fileHooks = createSuiteHooks();
1932
+ mergeHooks(fileHooks, getHooks(defaultTasks));
1933
+ for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) if (c.type === "test" || c.type === "suite") file.tasks.push(c);
1934
+ else if (c.type === "collector") {
1935
+ const suite = await c.collect(file);
1936
+ if (suite.name || suite.tasks.length) {
1937
+ mergeHooks(fileHooks, getHooks(suite));
1938
+ file.tasks.push(suite);
1939
+ }
1940
+ }
1941
+ setHooks(file, fileHooks);
1942
+ file.collectDuration = now$1() - collectStart;
1943
+ } catch (e) {
1944
+ file.result = {
1945
+ state: "fail",
1946
+ errors: e instanceof AggregateError ? e.errors.map((e) => processError(e, runner.config._diffOptions)) : [processError(e, runner.config._diffOptions)]
1947
+ };
1948
+ const durations = runner.getImportDurations?.();
1949
+ if (durations) file.importDurations = durations;
1950
+ }
1951
+ calculateSuiteHash(file);
1952
+ const hasOnlyTasks = someTasksAreOnly(file);
1953
+ if (!testTagsFilter && !defaultTagsFilter && config.tagsFilter) defaultTagsFilter = createTagsFilter(config.tagsFilter, config.tags);
1954
+ interpretTaskModes(file, testNamePattern ?? config.testNamePattern, testLocations, testIds, testTagsFilter ?? defaultTagsFilter, hasOnlyTasks, false, config.allowOnly);
1955
+ if (file.mode === "queued") file.mode = "run";
1956
+ files.push(file);
1957
+ });
1958
+ }
1959
+ return files;
1960
+ }
1961
+ function mergeHooks(baseHooks, hooks) {
1962
+ for (const _key in hooks) {
1963
+ const key = _key;
1964
+ baseHooks[key].push(...hooks[key]);
1965
+ }
1966
+ return baseHooks;
1967
+ }
1968
+
1969
+ /**
1970
+ * Partition in tasks groups by consecutive concurrent
1971
+ */
1972
+ function partitionSuiteChildren(suite) {
1973
+ let tasksGroup = [];
1974
+ const tasksGroups = [];
1975
+ for (const c of suite.tasks) if (tasksGroup.length === 0 || c.concurrent === tasksGroup[0].concurrent) tasksGroup.push(c);
1976
+ else {
1977
+ tasksGroups.push(tasksGroup);
1978
+ tasksGroup = [c];
1979
+ }
1980
+ if (tasksGroup.length > 0) tasksGroups.push(tasksGroup);
1981
+ return tasksGroups;
1982
+ }
1983
+
1984
+ const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1985
+ const unixNow = Date.now;
1986
+ const { clearTimeout, setTimeout } = getSafeTimers();
1987
+ let limitMaxConcurrency;
1988
+ /**
1989
+ * Normalizes retry configuration to extract individual values.
1990
+ * Handles both number and object forms.
1991
+ */
1992
+ function getRetryCount(retry) {
1993
+ if (retry === void 0) return 0;
1994
+ if (typeof retry === "number") return retry;
1995
+ return retry.count ?? 0;
1996
+ }
1997
+ function getRetryDelay(retry) {
1998
+ if (retry === void 0) return 0;
1999
+ if (typeof retry === "number") return 0;
2000
+ return retry.delay ?? 0;
2001
+ }
2002
+ function getRetryCondition(retry) {
2003
+ if (retry === void 0) return;
2004
+ if (typeof retry === "number") return;
2005
+ return retry.condition;
2006
+ }
2007
+ function updateSuiteHookState(task, name, state, runner) {
2008
+ if (!task.result) task.result = { state: "run" };
2009
+ if (!task.result.hooks) task.result.hooks = {};
2010
+ const suiteHooks = task.result.hooks;
2011
+ if (suiteHooks) {
2012
+ suiteHooks[name] = state;
2013
+ let event = state === "run" ? "before-hook-start" : "before-hook-end";
2014
+ if (name === "afterAll" || name === "afterEach") event = state === "run" ? "after-hook-start" : "after-hook-end";
2015
+ updateTask(event, task, runner);
2016
+ }
2017
+ }
2018
+ function getSuiteHooks(suite, name, sequence) {
2019
+ const hooks = getHooks(suite)[name];
2020
+ if (sequence === "stack" && (name === "afterAll" || name === "afterEach")) return hooks.slice().reverse();
2021
+ return hooks;
2022
+ }
2023
+ async function callTestHooks(runner, test, hooks, sequence) {
2024
+ if (sequence === "stack") hooks = hooks.slice().reverse();
2025
+ if (!hooks.length) return;
2026
+ const context = test.context;
2027
+ const onTestFailed = test.context.onTestFailed;
2028
+ const onTestFinished = test.context.onTestFinished;
2029
+ context.onTestFailed = () => {
2030
+ throw new Error(`Cannot call "onTestFailed" inside a test hook.`);
2031
+ };
2032
+ context.onTestFinished = () => {
2033
+ throw new Error(`Cannot call "onTestFinished" inside a test hook.`);
2034
+ };
2035
+ if (sequence === "parallel") try {
2036
+ await Promise.all(hooks.map((fn) => limitMaxConcurrency(() => fn(test.context))));
2037
+ } catch (e) {
2038
+ failTask(test.result, e, runner.config._diffOptions);
2039
+ }
2040
+ else for (const fn of hooks) try {
2041
+ await limitMaxConcurrency(() => fn(test.context));
2042
+ } catch (e) {
2043
+ failTask(test.result, e, runner.config._diffOptions);
2044
+ }
2045
+ context.onTestFailed = onTestFailed;
2046
+ context.onTestFinished = onTestFinished;
2047
+ }
2048
+ async function callSuiteHook(suite, currentTask, name, runner, args) {
2049
+ const sequence = runner.config.sequence.hooks;
2050
+ const callbacks = [];
2051
+ // stop at file level
2052
+ const parentSuite = "filepath" in suite ? null : suite.suite || suite.file;
2053
+ if (name === "beforeEach" && parentSuite) callbacks.push(...await callSuiteHook(parentSuite, currentTask, name, runner, args));
2054
+ const hooks = getSuiteHooks(suite, name, sequence);
2055
+ if (hooks.length > 0) updateSuiteHookState(currentTask, name, "run", runner);
2056
+ async function runHook(hook) {
2057
+ return limitMaxConcurrency(async () => {
2058
+ return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : void 0);
2059
+ });
2060
+ }
2061
+ if (sequence === "parallel") callbacks.push(...await Promise.all(hooks.map((hook) => runHook(hook))));
2062
+ else for (const hook of hooks) callbacks.push(await runHook(hook));
2063
+ if (hooks.length > 0) updateSuiteHookState(currentTask, name, "pass", runner);
2064
+ if (name === "afterEach" && parentSuite) callbacks.push(...await callSuiteHook(parentSuite, currentTask, name, runner, args));
2065
+ return callbacks;
2066
+ }
2067
+ function getAroundEachHooks(suite) {
2068
+ const hooks = [];
2069
+ const parentSuite = "filepath" in suite ? null : suite.suite || suite.file;
2070
+ if (parentSuite) hooks.push(...getAroundEachHooks(parentSuite));
2071
+ hooks.push(...getHooks(suite).aroundEach);
2072
+ return hooks;
2073
+ }
2074
+ function getAroundAllHooks(suite) {
2075
+ return getHooks(suite).aroundAll;
2076
+ }
2077
+ function makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError) {
2078
+ const message = `The ${phase} phase of "${hookName}" hook timed out after ${timeout}ms.`;
2079
+ const error = new (phase === "setup" ? AroundHookSetupError : AroundHookTeardownError)(message);
2080
+ if (stackTraceError?.stack) error.stack = stackTraceError.stack.replace(stackTraceError.message, error.message);
2081
+ return error;
2082
+ }
2083
+ async function callAroundHooks(runInner, options) {
2084
+ const { hooks, hookName, callbackName, onTimeout, invokeHook } = options;
2085
+ if (!hooks.length) {
2086
+ await runInner();
2087
+ return;
2088
+ }
2089
+ const hookErrors = [];
2090
+ const createTimeoutPromise = (timeout, phase, stackTraceError) => {
2091
+ let timer;
2092
+ let timedout = false;
2093
+ const promise = new Promise((_, reject) => {
2094
+ if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) {
2095
+ timer = setTimeout(() => {
2096
+ timedout = true;
2097
+ const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError);
2098
+ onTimeout?.(error);
2099
+ reject(error);
2100
+ }, timeout);
2101
+ timer.unref?.();
2102
+ }
2103
+ });
2104
+ const clear = () => {
2105
+ if (timer) {
2106
+ clearTimeout(timer);
2107
+ timer = void 0;
2108
+ }
2109
+ };
2110
+ return {
2111
+ promise,
2112
+ clear,
2113
+ isTimedOut: () => timedout
2114
+ };
2115
+ };
2116
+ const runNextHook = async (index) => {
2117
+ if (index >= hooks.length) return runInner();
2118
+ const hook = hooks[index];
2119
+ const timeout = getAroundHookTimeout(hook);
2120
+ const stackTraceError = getAroundHookStackTrace(hook);
2121
+ let useCalled = false;
2122
+ let setupTimeout;
2123
+ let teardownTimeout;
2124
+ let setupLimitConcurrencyRelease;
2125
+ let teardownLimitConcurrencyRelease;
2126
+ // Promise that resolves when use() is called (setup phase complete)
2127
+ let resolveUseCalled;
2128
+ const useCalledPromise = new Promise((resolve) => {
2129
+ resolveUseCalled = resolve;
2130
+ });
2131
+ // Promise that resolves when use() returns (inner hooks complete, teardown phase starts)
2132
+ let resolveUseReturned;
2133
+ const useReturnedPromise = new Promise((resolve) => {
2134
+ resolveUseReturned = resolve;
2135
+ });
2136
+ // Promise that resolves when hook completes
2137
+ let resolveHookComplete;
2138
+ let rejectHookComplete;
2139
+ const hookCompletePromise = new Promise((resolve, reject) => {
2140
+ resolveHookComplete = resolve;
2141
+ rejectHookComplete = reject;
2142
+ });
2143
+ const use = async () => {
2144
+ // shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.
2145
+ if (setupTimeout.isTimedOut())
2146
+ // we can throw any error to bail out.
2147
+ // this error is not seen by end users since `runNextHook` already rejected with timeout error
2148
+ // and this error is caught by `rejectHookComplete`.
2149
+ throw new Error("__VITEST_INTERNAL_AROUND_HOOK_ABORT__");
2150
+ if (useCalled) throw new AroundHookMultipleCallsError(`The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. The callback can only be called once per hook.`);
2151
+ useCalled = true;
2152
+ resolveUseCalled();
2153
+ // Setup phase completed - clear setup timer
2154
+ setupTimeout.clear();
2155
+ setupLimitConcurrencyRelease?.();
2156
+ // Run inner hooks - don't time this against our teardown timeout
2157
+ await runNextHook(index + 1).catch((e) => hookErrors.push(e));
2158
+ teardownLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
2159
+ // Start teardown timer after inner hooks complete - only times this hook's teardown code
2160
+ teardownTimeout = createTimeoutPromise(timeout, "teardown", stackTraceError);
2161
+ // Signal that use() is returning (teardown phase starting)
2162
+ resolveUseReturned();
2163
+ };
2164
+ setupLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
2165
+ // Start setup timeout
2166
+ setupTimeout = createTimeoutPromise(timeout, "setup", stackTraceError);
2167
+ (async () => {
2168
+ try {
2169
+ await invokeHook(hook, use);
2170
+ if (!useCalled) throw new AroundHookSetupError(`The \`${callbackName}\` callback was not called in the \`${hookName}\` hook. Make sure to call \`${callbackName}\` to run the ${hookName === "aroundEach" ? "test" : "suite"}.`);
2171
+ resolveHookComplete();
2172
+ } catch (error) {
2173
+ rejectHookComplete(error);
2174
+ } finally {
2175
+ setupLimitConcurrencyRelease?.();
2176
+ teardownLimitConcurrencyRelease?.();
2177
+ }
2178
+ })();
2179
+ // Wait for either: use() to be called OR hook to complete (error) OR setup timeout
2180
+ try {
2181
+ await Promise.race([
2182
+ useCalledPromise,
2183
+ hookCompletePromise,
2184
+ setupTimeout.promise
2185
+ ]);
2186
+ } finally {
2187
+ setupLimitConcurrencyRelease?.();
2188
+ setupTimeout.clear();
2189
+ }
2190
+ // Wait for use() to return (inner hooks complete) OR hook to complete (error during inner hooks)
2191
+ await Promise.race([useReturnedPromise, hookCompletePromise]);
2192
+ // Now teardownTimeout is guaranteed to be set
2193
+ // Wait for hook to complete (teardown) OR teardown timeout
2194
+ try {
2195
+ await Promise.race([hookCompletePromise, teardownTimeout?.promise]);
2196
+ } finally {
2197
+ teardownLimitConcurrencyRelease?.();
2198
+ teardownTimeout?.clear();
2199
+ }
2200
+ };
2201
+ await runNextHook(0).catch((e) => hookErrors.push(e));
2202
+ if (hookErrors.length > 0) throw hookErrors;
2203
+ }
2204
+ async function callAroundAllHooks(suite, runSuiteInner) {
2205
+ await callAroundHooks(runSuiteInner, {
2206
+ hooks: getAroundAllHooks(suite),
2207
+ hookName: "aroundAll",
2208
+ callbackName: "runSuite()",
2209
+ invokeHook: (hook, use) => hook(use, suite)
2210
+ });
2211
+ }
2212
+ async function callAroundEachHooks(suite, test, runTest) {
2213
+ await callAroundHooks(
2214
+ // Take checkpoint right before runTest - at this point all aroundEach fixtures
2215
+ // have been resolved, so we can correctly identify which fixtures belong to
2216
+ // aroundEach (before checkpoint) vs inside runTest (after checkpoint)
2217
+ () => runTest(getFixtureCleanupCount(test.context)),
2218
+ {
2219
+ hooks: getAroundEachHooks(suite),
2220
+ hookName: "aroundEach",
2221
+ callbackName: "runTest()",
2222
+ onTimeout: (error) => abortContextSignal(test.context, error),
2223
+ invokeHook: (hook, use) => hook(use, test.context, suite)
2224
+ }
2225
+ );
2226
+ }
2227
+ const packs = /* @__PURE__ */ new Map();
2228
+ const eventsPacks = [];
2229
+ const pendingTasksUpdates = [];
2230
+ function sendTasksUpdate(runner) {
2231
+ if (packs.size) {
2232
+ const taskPacks = Array.from(packs).map(([id, task]) => {
2233
+ return [
2234
+ id,
2235
+ task[0],
2236
+ task[1]
2237
+ ];
2238
+ });
2239
+ const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
2240
+ if (p) {
2241
+ pendingTasksUpdates.push(p);
2242
+ // remove successful promise to not grow array indefinitely,
2243
+ // but keep rejections so finishSendTasksUpdate can handle them
2244
+ p.then(() => pendingTasksUpdates.splice(pendingTasksUpdates.indexOf(p), 1), () => {});
2245
+ }
2246
+ eventsPacks.length = 0;
2247
+ packs.clear();
2248
+ }
2249
+ }
2250
+ async function finishSendTasksUpdate(runner) {
2251
+ sendTasksUpdate(runner);
2252
+ await Promise.all(pendingTasksUpdates);
2253
+ }
2254
+ function throttle(fn, ms) {
2255
+ let last = 0;
2256
+ let pendingCall;
2257
+ return function call(...args) {
2258
+ const now = unixNow();
2259
+ if (now - last > ms) {
2260
+ last = now;
2261
+ clearTimeout(pendingCall);
2262
+ pendingCall = void 0;
2263
+ return fn.apply(this, args);
2264
+ }
2265
+ // Make sure fn is still called even if there are no further calls
2266
+ pendingCall ??= setTimeout(() => call.bind(this)(...args), ms);
2267
+ };
2268
+ }
2269
+ // throttle based on summary reporter's DURATION_UPDATE_INTERVAL_MS
2270
+ const sendTasksUpdateThrottled = throttle(sendTasksUpdate, 100);
2271
+ function updateTask(event, task, runner) {
2272
+ eventsPacks.push([
2273
+ task.id,
2274
+ event,
2275
+ void 0
2276
+ ]);
2277
+ packs.set(task.id, [task.result, task.meta]);
2278
+ sendTasksUpdateThrottled(runner);
2279
+ }
2280
+ async function callCleanupHooks(runner, cleanups) {
2281
+ const sequence = runner.config.sequence.hooks;
2282
+ if (sequence === "stack") cleanups = cleanups.slice().reverse();
2283
+ if (sequence === "parallel") await Promise.all(cleanups.map(async (fn) => {
2284
+ if (typeof fn !== "function") return;
2285
+ await limitMaxConcurrency(() => fn());
2286
+ }));
2287
+ else for (const fn of cleanups) {
2288
+ if (typeof fn !== "function") continue;
2289
+ await limitMaxConcurrency(() => fn());
2290
+ }
2291
+ }
2292
+ /**
2293
+ * Determines if a test should be retried based on its retryCondition configuration
2294
+ */
2295
+ function passesRetryCondition(test, errors) {
2296
+ const condition = getRetryCondition(test.retry);
2297
+ if (!errors || errors.length === 0) return false;
2298
+ if (!condition) return true;
2299
+ const error = errors[errors.length - 1];
2300
+ if (condition instanceof RegExp) return condition.test(error.message || "");
2301
+ else if (typeof condition === "function") return condition(error);
2302
+ return false;
2303
+ }
2304
+ async function runTest(test, runner) {
2305
+ await runner.onBeforeRunTask?.(test);
2306
+ if (test.mode !== "run" && test.mode !== "queued") {
2307
+ updateTask("test-prepare", test, runner);
2308
+ updateTask("test-finished", test, runner);
2309
+ return;
2310
+ }
2311
+ if (test.result?.state === "fail") {
2312
+ // should not be possible to get here, I think this is just copy pasted from suite
2313
+ // TODO: maybe someone fails tests in `beforeAll` hooks?
2314
+ // https://github.com/vitest-dev/vitest/pull/7069
2315
+ updateTask("test-failed-early", test, runner);
2316
+ return;
2317
+ }
2318
+ const start = now();
2319
+ test.result = {
2320
+ state: "run",
2321
+ startTime: unixNow(),
2322
+ retryCount: 0
2323
+ };
2324
+ updateTask("test-prepare", test, runner);
2325
+ const cleanupRunningTest = addRunningTest(test);
2326
+ setCurrentTest(test);
2327
+ const suite = test.suite || test.file;
2328
+ const $ = runner.trace;
2329
+ const repeats = test.repeats ?? 0;
2330
+ for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
2331
+ const retry = getRetryCount(test.retry);
2332
+ for (let retryCount = 0; retryCount <= retry; retryCount++) {
2333
+ let beforeEachCleanups = [];
2334
+ // fixtureCheckpoint is passed by callAroundEachHooks - it represents the count
2335
+ // of fixture cleanup functions AFTER all aroundEach fixtures have been resolved
2336
+ // but BEFORE the test runs. This allows us to clean up only fixtures created
2337
+ // inside runTest while preserving aroundEach fixtures for teardown.
2338
+ await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => {
2339
+ try {
2340
+ await runner.onBeforeTryTask?.(test, {
2341
+ retry: retryCount,
2342
+ repeats: repeatCount
2343
+ });
2344
+ test.result.repeatCount = repeatCount;
2345
+ beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
2346
+ if (runner.runTask) await $("test.callback", () => limitMaxConcurrency(() => runner.runTask(test)));
2347
+ else {
2348
+ const fn = getFn(test);
2349
+ if (!fn) throw new Error("Test function is not found. Did you add it using `setFn`?");
2350
+ await $("test.callback", () => limitMaxConcurrency(() => fn()));
2351
+ }
2352
+ await runner.onAfterTryTask?.(test, {
2353
+ retry: retryCount,
2354
+ repeats: repeatCount
2355
+ });
2356
+ if (test.result.state !== "fail") test.result.state = "pass";
2357
+ } catch (e) {
2358
+ failTask(test.result, e, runner.config._diffOptions);
2359
+ }
2360
+ try {
2361
+ await runner.onTaskFinished?.(test);
2362
+ } catch (e) {
2363
+ failTask(test.result, e, runner.config._diffOptions);
2364
+ }
2365
+ try {
2366
+ await $("test.afterEach", () => callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]));
2367
+ if (beforeEachCleanups.length) await $("test.cleanup", () => callCleanupHooks(runner, beforeEachCleanups));
2368
+ // Only clean up fixtures created inside runTest (after the checkpoint)
2369
+ // Fixtures created for aroundEach will be cleaned up after aroundEach teardown
2370
+ await callFixtureCleanupFrom(test.context, fixtureCheckpoint);
2371
+ } catch (e) {
2372
+ failTask(test.result, e, runner.config._diffOptions);
2373
+ }
2374
+ if (test.onFinished?.length) await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
2375
+ if (test.result.state === "fail" && test.onFailed?.length) await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
2376
+ test.onFailed = void 0;
2377
+ test.onFinished = void 0;
2378
+ await runner.onAfterRetryTask?.(test, {
2379
+ retry: retryCount,
2380
+ repeats: repeatCount
2381
+ });
2382
+ }).catch((error) => {
2383
+ failTask(test.result, error, runner.config._diffOptions);
2384
+ });
2385
+ // Clean up fixtures that were created for aroundEach (before the checkpoint)
2386
+ // This runs after aroundEach teardown has completed
2387
+ try {
2388
+ await callFixtureCleanup(test.context);
2389
+ } catch (e) {
2390
+ failTask(test.result, e, runner.config._diffOptions);
2391
+ }
2392
+ // skipped with new PendingError
2393
+ if (test.result?.pending || test.result?.state === "skip") {
2394
+ test.mode = "skip";
2395
+ test.result = {
2396
+ state: "skip",
2397
+ note: test.result?.note,
2398
+ pending: true,
2399
+ duration: now() - start
2400
+ };
2401
+ updateTask("test-finished", test, runner);
2402
+ setCurrentTest(void 0);
2403
+ cleanupRunningTest();
2404
+ return;
2405
+ }
2406
+ if (test.result.state === "pass") break;
2407
+ if (retryCount < retry) {
2408
+ if (!passesRetryCondition(test, test.result.errors)) break;
2409
+ test.result.state = "run";
2410
+ test.result.retryCount = (test.result.retryCount ?? 0) + 1;
2411
+ const delay = getRetryDelay(test.retry);
2412
+ if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
2413
+ }
2414
+ // update retry info
2415
+ updateTask("test-retried", test, runner);
2416
+ }
2417
+ }
2418
+ // if test is marked to be failed, flip the result unless `TestSyntaxError` is present
2419
+ if (test.fails) {
2420
+ if (test.result.state === "pass") {
2421
+ const error = processError(/* @__PURE__ */ new Error("Expect test to fail"));
2422
+ test.result.state = "fail";
2423
+ test.result.errors = [error];
2424
+ } else if (!test.result.errors?.some((e) => e.__vitest_test_syntax_error__)) {
2425
+ test.result.state = "pass";
2426
+ test.result.errors = void 0;
2427
+ }
2428
+ }
2429
+ cleanupRunningTest();
2430
+ setCurrentTest(void 0);
2431
+ test.result.duration = now() - start;
2432
+ await runner.onAfterRunTask?.(test);
2433
+ updateTask("test-finished", test, runner);
2434
+ }
2435
+ function failTask(result, err, diffOptions) {
2436
+ if (err instanceof PendingError) {
2437
+ result.state = "skip";
2438
+ result.note = err.note;
2439
+ result.pending = true;
2440
+ return;
2441
+ }
2442
+ if (err instanceof TestRunAbortError) {
2443
+ result.state = "skip";
2444
+ result.note = err.message;
2445
+ return;
2446
+ }
2447
+ result.state = "fail";
2448
+ const errors = Array.isArray(err) ? err : [err];
2449
+ for (const e of errors) {
2450
+ const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, diffOptions)) : [processError(e, diffOptions)];
2451
+ result.errors ??= [];
2452
+ result.errors.push(...errors);
2453
+ }
2454
+ }
2455
+ function markTasksAsSkipped(suite, runner) {
2456
+ suite.tasks.forEach((t) => {
2457
+ t.mode = "skip";
2458
+ t.result = {
2459
+ ...t.result,
2460
+ state: "skip"
2461
+ };
2462
+ updateTask("test-finished", t, runner);
2463
+ if (t.type === "suite") markTasksAsSkipped(t, runner);
2464
+ });
2465
+ }
2466
+ function markPendingTasksAsSkipped(suite, runner, note) {
2467
+ suite.tasks.forEach((t) => {
2468
+ if (!t.result || t.result.state === "run") {
2469
+ t.mode = "skip";
2470
+ t.result = {
2471
+ ...t.result,
2472
+ state: "skip",
2473
+ note
2474
+ };
2475
+ updateTask("test-cancel", t, runner);
2476
+ }
2477
+ if (t.type === "suite") markPendingTasksAsSkipped(t, runner, note);
2478
+ });
2479
+ }
2480
+ async function runSuite(suite, runner) {
2481
+ await runner.onBeforeRunSuite?.(suite);
2482
+ if (suite.result?.state === "fail") {
2483
+ markTasksAsSkipped(suite, runner);
2484
+ // failed during collection
2485
+ updateTask("suite-failed-early", suite, runner);
2486
+ return;
2487
+ }
2488
+ const start = now();
2489
+ const mode = suite.mode;
2490
+ suite.result = {
2491
+ state: mode === "skip" || mode === "todo" ? mode : "run",
2492
+ startTime: unixNow()
2493
+ };
2494
+ const $ = runner.trace;
2495
+ updateTask("suite-prepare", suite, runner);
2496
+ let beforeAllCleanups = [];
2497
+ if (suite.mode === "skip") {
2498
+ suite.result.state = "skip";
2499
+ updateTask("suite-finished", suite, runner);
2500
+ } else if (suite.mode === "todo") {
2501
+ suite.result.state = "todo";
2502
+ updateTask("suite-finished", suite, runner);
2503
+ } else {
2504
+ let suiteRan = false;
2505
+ try {
2506
+ await callAroundAllHooks(suite, async () => {
2507
+ suiteRan = true;
2508
+ try {
2509
+ // beforeAll
2510
+ try {
2511
+ beforeAllCleanups = await $("suite.beforeAll", () => callSuiteHook(suite, suite, "beforeAll", runner, [suite]));
2512
+ } catch (e) {
2513
+ failTask(suite.result, e, runner.config._diffOptions);
2514
+ markTasksAsSkipped(suite, runner);
2515
+ return;
2516
+ }
2517
+ // run suite children
2518
+ if (runner.runSuite) await runner.runSuite(suite);
2519
+ else for (let tasksGroup of partitionSuiteChildren(suite)) if (tasksGroup[0].concurrent === true) {
2520
+ const groupLimiter = limitConcurrency(runner.config.maxConcurrency);
2521
+ await Promise.all(tasksGroup.map((c) => groupLimiter(() => runSuiteChild(c, runner))));
2522
+ } else {
2523
+ const { sequence } = runner.config;
2524
+ if (suite.shuffle) tasksGroup = shuffle([tasksGroup.filter((group) => group.type === "suite"), tasksGroup.filter((group) => group.type === "test")], sequence.seed).flatMap((group) => shuffle(group, sequence.seed));
2525
+ for (const c of tasksGroup) await runSuiteChild(c, runner);
2526
+ }
2527
+ } finally {
2528
+ // afterAll runs even if beforeAll or suite children fail
2529
+ try {
2530
+ await $("suite.afterAll", () => callSuiteHook(suite, suite, "afterAll", runner, [suite]));
2531
+ if (beforeAllCleanups.length) await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
2532
+ if (suite.file === suite) {
2533
+ const contexts = TestFixtures.getFileContexts(suite.file);
2534
+ await Promise.all(contexts.map((context) => callFixtureCleanup(context)));
2535
+ }
2536
+ } catch (e) {
2537
+ failTask(suite.result, e, runner.config._diffOptions);
2538
+ }
2539
+ }
2540
+ });
2541
+ } catch (e) {
2542
+ // mark tasks as skipped if aroundAll failed before the suite callback was executed
2543
+ if (!suiteRan) markTasksAsSkipped(suite, runner);
2544
+ failTask(suite.result, e, runner.config._diffOptions);
2545
+ }
2546
+ if (suite.mode === "run" || suite.mode === "queued") if (!runner.config.passWithNoTests && !hasTests(suite)) {
2547
+ suite.result.state = "fail";
2548
+ if (!suite.result.errors?.length) {
2549
+ const error = processError(/* @__PURE__ */ new Error(`No test found in suite ${suite.name}`));
2550
+ suite.result.errors = [error];
2551
+ }
2552
+ } else if (hasFailed(suite)) suite.result.state = "fail";
2553
+ else suite.result.state = "pass";
2554
+ suite.result.duration = now() - start;
2555
+ await runner.onAfterRunSuite?.(suite);
2556
+ updateTask("suite-finished", suite, runner);
2557
+ }
2558
+ }
2559
+ async function runSuiteChild(c, runner) {
2560
+ const $ = runner.trace;
2561
+ if (c.type === "test") return $("run.test", {
2562
+ "vitest.test.id": c.id,
2563
+ "vitest.test.name": c.name,
2564
+ "vitest.test.mode": c.mode,
2565
+ "vitest.test.timeout": c.timeout,
2566
+ "code.file.path": c.file.filepath,
2567
+ "code.line.number": c.location?.line,
2568
+ "code.column.number": c.location?.column
2569
+ }, () => runTest(c, runner));
2570
+ else if (c.type === "suite") return $("run.suite", {
2571
+ "vitest.suite.id": c.id,
2572
+ "vitest.suite.name": c.name,
2573
+ "vitest.suite.mode": c.mode,
2574
+ "code.file.path": c.file.filepath,
2575
+ "code.line.number": c.location?.line,
2576
+ "code.column.number": c.location?.column
2577
+ }, () => runSuite(c, runner));
2578
+ }
2579
+ async function runFiles(files, runner) {
2580
+ limitMaxConcurrency ??= limitConcurrency(runner.config.maxConcurrency);
2581
+ for (const file of files) {
2582
+ if (!file.tasks.length && !runner.config.passWithNoTests) {
2583
+ if (!file.result?.errors?.length) file.result = {
2584
+ state: "fail",
2585
+ errors: [processError(/* @__PURE__ */ new Error(`No test suite found in file ${file.filepath}`))]
2586
+ };
2587
+ }
2588
+ await runner.trace("run.spec", {
2589
+ "code.file.path": file.filepath,
2590
+ "vitest.suite.tasks.length": file.tasks.length
2591
+ }, () => runSuite(file, runner));
2592
+ }
2593
+ }
2594
+ const workerRunners = /* @__PURE__ */ new WeakSet();
2595
+ function defaultTrace(_, attributes, cb) {
2596
+ if (typeof attributes === "function") return attributes();
2597
+ return cb();
2598
+ }
2599
+ async function startTests(specs, runner) {
2600
+ runner.trace ??= defaultTrace;
2601
+ const cancel = runner.cancel?.bind(runner);
2602
+ // Ideally, we need to have an event listener for this, but only have a runner here.
2603
+ // Adding another onCancel felt wrong (maybe it needs to be refactored)
2604
+ runner.cancel = (reason) => {
2605
+ // We intentionally create only one error since there is only one test run that can be cancelled
2606
+ const error = new TestRunAbortError("The test run was aborted by the user.", reason);
2607
+ getRunningTests().forEach((test) => {
2608
+ abortContextSignal(test.context, error);
2609
+ markPendingTasksAsSkipped(test.file, runner, error.message);
2610
+ });
2611
+ return cancel?.(reason);
2612
+ };
2613
+ if (!workerRunners.has(runner)) {
2614
+ runner.onCleanupWorkerContext?.(async () => {
2615
+ await Promise.all([...TestFixtures.getWorkerContexts()].map((context) => callFixtureCleanup(context))).finally(() => {
2616
+ TestFixtures.clearDefinitions();
2617
+ });
2618
+ });
2619
+ workerRunners.add(runner);
2620
+ }
2621
+ try {
2622
+ const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
2623
+ await runner.onBeforeCollect?.(paths);
2624
+ const files = await collectTests(specs, runner);
2625
+ await runner.onCollected?.(files);
2626
+ await runner.onBeforeRunFiles?.(files);
2627
+ await runFiles(files, runner);
2628
+ await runner.onAfterRunFiles?.(files);
2629
+ await finishSendTasksUpdate(runner);
2630
+ return files;
2631
+ } finally {
2632
+ runner.cancel = cancel;
2633
+ }
2634
+ }
2635
+ async function publicCollect(specs, runner) {
2636
+ runner.trace ??= defaultTrace;
2637
+ const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
2638
+ await runner.onBeforeCollect?.(paths);
2639
+ const files = await collectTests(specs, runner);
2640
+ await runner.onCollected?.(files);
2641
+ return files;
2642
+ }
2643
+
2644
+ /**
2645
+ * @experimental
2646
+ * @advanced
2647
+ *
2648
+ * Records a custom test artifact during test execution.
2649
+ *
2650
+ * This function allows you to attach structured data, files, or metadata to a test.
2651
+ *
2652
+ * Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
2653
+ *
2654
+ * **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
2655
+ *
2656
+ * @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
2657
+ * @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
2658
+ *
2659
+ * @returns A promise that resolves to the recorded artifact with location injected
2660
+ *
2661
+ * @throws {Error} If the test runner doesn't support artifacts
2662
+ *
2663
+ * @example
2664
+ * ```ts
2665
+ * // In a custom assertion
2666
+ * async function toHaveValidSchema(this: MatcherState, actual: unknown) {
2667
+ * const validation = validateSchema(actual)
2668
+ *
2669
+ * await recordArtifact(this.task, {
2670
+ * type: 'my-plugin:schema-validation',
2671
+ * passed: validation.valid,
2672
+ * errors: validation.errors,
2673
+ * })
2674
+ *
2675
+ * return { pass: validation.valid, message: () => '...' }
2676
+ * }
2677
+ * ```
2678
+ */
2679
+ async function recordArtifact(task, artifact) {
2680
+ const runner = getRunner();
2681
+ const stack = findTestFileStackTrace(task.file.filepath, (/* @__PURE__ */ new Error("STACK_TRACE")).stack);
2682
+ if (stack) {
2683
+ artifact.location = {
2684
+ file: stack.file,
2685
+ line: stack.line,
2686
+ column: stack.column
2687
+ };
2688
+ if (artifact.type === "internal:annotation") artifact.annotation.location = artifact.location;
2689
+ }
2690
+ if (Array.isArray(artifact.attachments)) for (const attachment of artifact.attachments) manageArtifactAttachment(attachment);
2691
+ // annotations won't resolve as artifacts for backwards compatibility until next major
2692
+ if (artifact.type === "internal:annotation") return artifact;
2693
+ if (!runner.onTestArtifactRecord) throw new Error(`Test runner doesn't support test artifacts.`);
2694
+ await finishSendTasksUpdate(runner);
2695
+ const resolvedArtifact = await runner.onTestArtifactRecord(task, artifact);
2696
+ task.artifacts.push(resolvedArtifact);
2697
+ return resolvedArtifact;
2698
+ }
2699
+ const table = [];
2700
+ for (let i = 65; i < 91; i++) table.push(String.fromCharCode(i));
2701
+ for (let i = 97; i < 123; i++) table.push(String.fromCharCode(i));
2702
+ for (let i = 0; i < 10; i++) table.push(i.toString(10));
2703
+ table.push("+", "/");
2704
+ function encodeUint8Array(bytes) {
2705
+ let base64 = "";
2706
+ const len = bytes.byteLength;
2707
+ for (let i = 0; i < len; i += 3) if (len === i + 1) {
2708
+ const a = (bytes[i] & 252) >> 2;
2709
+ const b = (bytes[i] & 3) << 4;
2710
+ base64 += table[a];
2711
+ base64 += table[b];
2712
+ base64 += "==";
2713
+ } else if (len === i + 2) {
2714
+ const a = (bytes[i] & 252) >> 2;
2715
+ const b = (bytes[i] & 3) << 4 | (bytes[i + 1] & 240) >> 4;
2716
+ const c = (bytes[i + 1] & 15) << 2;
2717
+ base64 += table[a];
2718
+ base64 += table[b];
2719
+ base64 += table[c];
2720
+ base64 += "=";
2721
+ } else {
2722
+ const a = (bytes[i] & 252) >> 2;
2723
+ const b = (bytes[i] & 3) << 4 | (bytes[i + 1] & 240) >> 4;
2724
+ const c = (bytes[i + 1] & 15) << 2 | (bytes[i + 2] & 192) >> 6;
2725
+ const d = bytes[i + 2] & 63;
2726
+ base64 += table[a];
2727
+ base64 += table[b];
2728
+ base64 += table[c];
2729
+ base64 += table[d];
2730
+ }
2731
+ return base64;
2732
+ }
2733
+ /**
2734
+ * Records an async operation associated with a test task.
2735
+ *
2736
+ * This function tracks promises that should be awaited before a test completes.
2737
+ * The promise is automatically removed from the test's promise list once it settles.
2738
+ */
2739
+ function recordAsyncOperation(test, promise) {
2740
+ // if promise is explicitly awaited, remove it from the list
2741
+ promise = promise.finally(() => {
2742
+ if (!test.promises) return;
2743
+ const index = test.promises.indexOf(promise);
2744
+ if (index !== -1) test.promises.splice(index, 1);
2745
+ });
2746
+ // record promise
2747
+ if (!test.promises) test.promises = [];
2748
+ test.promises.push(promise);
2749
+ return promise;
2750
+ }
2751
+ /**
2752
+ * Validates and prepares a test attachment for serialization.
2753
+ *
2754
+ * This function ensures attachments have either `body` or `path` set (but not both), and converts `Uint8Array` bodies to base64-encoded strings for easier serialization.
2755
+ *
2756
+ * @param attachment - The attachment to validate and prepare
2757
+ *
2758
+ * @throws {TypeError} If neither `body` nor `path` is provided
2759
+ * @throws {TypeError} If both `body` and `path` are provided
2760
+ */
2761
+ function manageArtifactAttachment(attachment) {
2762
+ if (attachment.body == null && !attachment.path) throw new TypeError(`Test attachment requires "body" or "path" to be set. Both are missing.`);
2763
+ if (attachment.body && attachment.path) throw new TypeError(`Test attachment requires only one of "body" or "path" to be set. Both are specified.`);
2764
+ if (attachment.path && attachment.bodyEncoding) throw new TypeError(`Test attachment with "path" should not have "bodyEncoding" specified.`);
2765
+ // convert to a string so it's easier to serialise
2766
+ if (attachment.body instanceof Uint8Array) attachment.body = encodeUint8Array(attachment.body);
2767
+ if (attachment.body != null) attachment.bodyEncoding ??= "base64";
2768
+ }
2769
+
2770
+ export { TestSyntaxError as T, getCurrentSuite as a, createChainable as b, createTaskCollector as c, getHooks as d, getFn as e, afterAll as f, getCurrentTest as g, afterEach as h, aroundAll as i, aroundEach as j, beforeAll as k, beforeEach as l, matchesTags as m, describe as n, it as o, publicCollect as p, onTestFailed as q, onTestFinished as r, startTests as s, recordArtifact as t, suite as u, test as v, validateTags as w, limitConcurrency as x, createTagsFilter as y };