@voidzero-dev/vite-plus-test 0.1.8 → 0.1.10

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 (205) hide show
  1. package/LICENSE.md +181 -1
  2. package/dist/@vitest/browser/client/.vite/manifest.json +6 -6
  3. package/dist/@vitest/browser/client/__vitest__/assets/index-Da0hb3oU.css +1 -0
  4. package/dist/@vitest/browser/client/__vitest__/assets/index-Di71CKDo.js +63 -0
  5. package/dist/@vitest/browser/client/__vitest__/favicon.ico +0 -0
  6. package/dist/@vitest/browser/client/__vitest__/favicon.svg +49 -4
  7. package/dist/@vitest/browser/client/__vitest__/index.html +2 -2
  8. package/dist/@vitest/browser/client/__vitest_browser__/{orchestrator-S_3e_uzt.js → orchestrator-CXs6qrFe.js} +70 -28
  9. package/dist/@vitest/browser/client/__vitest_browser__/{tester-k74mgIRa.js → tester-K5NNxh1O.js} +167 -58
  10. package/dist/@vitest/browser/client/__vitest_browser__/{utils-uxqdqUz8.js → utils-C2ISqq1C.js} +2 -2
  11. package/dist/@vitest/browser/client/favicon.svg +49 -4
  12. package/dist/@vitest/browser/client/orchestrator.html +2 -2
  13. package/dist/@vitest/browser/client/tester/tester.html +2 -2
  14. package/dist/@vitest/browser/client.js +20 -13
  15. package/dist/@vitest/browser/context.d.ts +160 -10
  16. package/dist/@vitest/browser/context.js +108 -22
  17. package/dist/@vitest/browser/expect-element.js +23 -28
  18. package/dist/@vitest/browser/index-5Pe7X7sp.js +7 -0
  19. package/dist/@vitest/browser/index.d.ts +20 -2
  20. package/dist/@vitest/browser/index.js +5706 -159
  21. package/dist/@vitest/browser/locators.d.ts +14 -3
  22. package/dist/@vitest/browser/locators.js +1 -1
  23. package/dist/@vitest/browser-playwright/index.d.ts +22 -5
  24. package/dist/@vitest/browser-playwright/index.js +169 -61
  25. package/dist/@vitest/browser-preview/index.d.ts +14 -1
  26. package/dist/@vitest/browser-preview/locators.js +31 -18
  27. package/dist/@vitest/browser-webdriverio/index.d.ts +17 -3
  28. package/dist/@vitest/browser-webdriverio/index.js +22 -2
  29. package/dist/@vitest/browser-webdriverio/locators.js +84 -7
  30. package/dist/@vitest/expect/index.d.ts +172 -54
  31. package/dist/@vitest/expect/index.js +124 -67
  32. package/dist/@vitest/mocker/auto-register.js +1 -0
  33. package/dist/@vitest/mocker/automock.d.ts +1 -0
  34. package/dist/@vitest/mocker/automock.js +5 -0
  35. package/dist/@vitest/mocker/browser.d.ts +4 -4
  36. package/dist/@vitest/mocker/browser.js +1 -0
  37. package/dist/@vitest/mocker/chunk-automock.js +182 -14
  38. package/dist/@vitest/mocker/chunk-helpers.js +44 -0
  39. package/dist/@vitest/mocker/chunk-hoistMocks.js +659 -0
  40. package/dist/@vitest/mocker/chunk-mocker.js +41 -30
  41. package/dist/@vitest/mocker/chunk-registry.js +21 -7
  42. package/dist/@vitest/mocker/chunk-utils.js +18 -7
  43. package/dist/@vitest/mocker/hoistMocks.d-w2ILr1dG.d.ts +739 -0
  44. package/dist/@vitest/mocker/{index.d-C-sLYZi-.d.ts → index.d-B41z0AuW.d.ts} +1 -1
  45. package/dist/@vitest/mocker/index.d.ts +2 -2
  46. package/dist/@vitest/mocker/index.js +18 -3
  47. package/dist/@vitest/mocker/{mocker.d-TnKRhz7N.d.ts → mocker.d-QEntlm6J.d.ts} +10 -5
  48. package/dist/@vitest/mocker/node.d.ts +5 -734
  49. package/dist/@vitest/mocker/node.js +29 -587
  50. package/dist/@vitest/mocker/redirect.js +4 -4
  51. package/dist/@vitest/mocker/register.d.ts +3 -3
  52. package/dist/@vitest/mocker/register.js +1 -0
  53. package/dist/@vitest/mocker/transforms.d.ts +6 -0
  54. package/dist/@vitest/mocker/transforms.js +8 -0
  55. package/dist/@vitest/mocker/{types.d-B8CCKmHt.d.ts → types.d-BjI5eAwu.d.ts} +23 -7
  56. package/dist/@vitest/pretty-format/index.d.ts +11 -1
  57. package/dist/@vitest/pretty-format/index.js +33 -4
  58. package/dist/@vitest/runner/chunk-tasks.js +305 -37
  59. package/dist/@vitest/runner/index.d.ts +5 -6
  60. package/dist/@vitest/runner/index.js +1146 -455
  61. package/dist/@vitest/runner/{tasks.d-C7UxawJ9.d.ts → tasks.d-D2GKpdwQ.d.ts} +726 -55
  62. package/dist/@vitest/runner/types.d.ts +2 -182
  63. package/dist/@vitest/runner/utils.d.ts +16 -8
  64. package/dist/@vitest/runner/utils.js +1 -1
  65. package/dist/@vitest/snapshot/{environment.d-DHdQ1Csl.d.ts → environment.d-DOJxxZV9.d.ts} +2 -7
  66. package/dist/@vitest/snapshot/environment.d.ts +2 -1
  67. package/dist/@vitest/snapshot/environment.js +1 -1
  68. package/dist/@vitest/snapshot/index.d.ts +4 -3
  69. package/dist/@vitest/snapshot/index.js +21 -550
  70. package/dist/@vitest/snapshot/manager.d.ts +3 -2
  71. package/dist/@vitest/snapshot/manager.js +1 -1
  72. package/dist/@vitest/snapshot/{rawSnapshot.d-lFsMJFUd.d.ts → rawSnapshot.d-U2kJUxDr.d.ts} +1 -1
  73. package/dist/@vitest/spy/index.d.ts +34 -4
  74. package/dist/@vitest/spy/index.js +69 -19
  75. package/dist/@vitest/utils/diff.js +11 -9
  76. package/dist/@vitest/utils/display.d.ts +2 -1
  77. package/dist/@vitest/utils/display.js +38 -5
  78. package/dist/@vitest/utils/error.d.ts +2 -1
  79. package/dist/@vitest/utils/error.js +1 -2
  80. package/dist/@vitest/utils/helpers.d.ts +4 -1
  81. package/dist/@vitest/utils/helpers.js +43 -1
  82. package/dist/@vitest/utils/resolver.js +1 -2
  83. package/dist/@vitest/utils/serialize.js +6 -6
  84. package/dist/@vitest/utils/source-map/node.d.ts +6 -0
  85. package/dist/@vitest/utils/source-map/node.js +23 -0
  86. package/dist/@vitest/utils/source-map.js +15 -5
  87. package/dist/browser.d.ts +3 -2
  88. package/dist/browser.js +2 -2
  89. package/dist/chunks/acorn.B2iPLyUM.js +5958 -0
  90. package/dist/chunks/{base.CJ0Y4ePK.js → base.DM0-RqVb.js} +54 -16
  91. package/dist/chunks/{benchmark.B3N2zMcH.js → benchmark.D0SlKNbZ.js} +1 -1
  92. package/dist/chunks/{browser.d.ChKACdzH.d.ts → browser.d.X3SXoOCV.d.ts} +4 -1
  93. package/dist/chunks/{cac.DVeoLl0M.js → cac.CWGDZnXT.js} +979 -20
  94. package/dist/chunks/{cli-api.B7PN_QUv.js → cli-api.DuT9iuvY.js} +8764 -7898
  95. package/dist/chunks/{config.d.Cy95HiCx.d.ts → config.d.EJLVE3es.d.ts} +30 -15
  96. package/dist/chunks/{console.Cf-YriPC.js → console.3WNpx0tS.js} +3 -2
  97. package/dist/chunks/{constants.D_Q9UYh-.js → constants.CPYnjOGj.js} +4 -2
  98. package/dist/chunks/coverage.Bri33R1t.js +1050 -0
  99. package/dist/chunks/{creator.DAmOKTvJ.js → creator.DgVhQm5q.js} +35 -4
  100. package/dist/chunks/{defaults.BOqNVLsY.js → defaults.CdU2lD-q.js} +4 -3
  101. package/dist/chunks/{global.d.B15mdLcR.d.ts → global.d.x-ILCfAE.d.ts} +1 -2
  102. package/dist/chunks/{globals.DOayXfHP.js → globals.BXNGLnTL.js} +11 -10
  103. package/dist/chunks/{coverage.AVPTjMgw.js → index.BCY_7LL2.js} +5 -959
  104. package/dist/chunks/{index.C5r1PdPD.js → index.CEzQDJGb.js} +1 -1
  105. package/dist/chunks/{index.D3XRDfWc.js → index.CMESou6r.js} +26 -1
  106. package/dist/chunks/{index.6Qv1eEA6.js → index.DGNSnENe.js} +95 -9
  107. package/dist/chunks/{index.M8mOzt4Y.js → index.DXMFO5MJ.js} +3279 -2914
  108. package/dist/chunks/{index.Z5E_ObnR.js → index.DlDSLQD3.js} +7 -3
  109. package/dist/chunks/{index.CyBMJtT7.js → index.EY6TCHpo.js} +10 -8
  110. package/dist/chunks/{index.D4KonVSU.js → index.og1WyBLx.js} +18 -3
  111. package/dist/chunks/{init-forks._y3TW739.js → init-forks.DeArv0jT.js} +1 -1
  112. package/dist/chunks/{init-threads.DBO2kn-p.js → init-threads.-2OUl4Nn.js} +1 -1
  113. package/dist/chunks/{init.B6MLFIaN.js → init.DICorXCo.js} +52 -13
  114. package/dist/chunks/native.DPzPHdi5.js +148 -0
  115. package/dist/chunks/nativeModuleMocker.DndvSdL6.js +206 -0
  116. package/dist/chunks/nativeModuleRunner.BIakptoF.js +36 -0
  117. package/dist/chunks/{node.Ce0vMQM7.js → node.COQbm6gK.js} +1 -1
  118. package/dist/chunks/{plugin.d.CtqpEehP.d.ts → plugin.d.BuW-flqo.d.ts} +1 -1
  119. package/dist/chunks/{reporters.d.CWXNI2jG.d.ts → reporters.d.DVUYHHhe.d.ts} +328 -79
  120. package/dist/chunks/rpc.MzXet3jl.js +144 -0
  121. package/dist/chunks/{rpc.d.RH3apGEf.d.ts → rpc.d.BFMWpdph.d.ts} +10 -2
  122. package/dist/chunks/{setup-common.Cm-kSBVi.js → setup-common.B41N_kPE.js} +3 -3
  123. package/dist/chunks/{startModuleRunner.DEj0jb3e.js → startVitestModuleRunner.C3ZR-4J3.js} +265 -405
  124. package/dist/chunks/{suite.d.BJWk38HB.d.ts → suite.d.udJtyAgw.d.ts} +1 -1
  125. package/dist/chunks/{vi.2VT5v0um.js → test.CTcmp4Su.js} +538 -181
  126. package/dist/chunks/{utils.DvEY5TfP.js → utils.BX5Fg8C4.js} +8 -1
  127. package/dist/chunks/{vm.D3epNOPZ.js → vm.Dh2rTtmP.js} +48 -8
  128. package/dist/chunks/{worker.d.Dyxm8DEL.d.ts → worker.d.B84sVRy0.d.ts} +2 -2
  129. package/dist/cli.js +6 -5
  130. package/dist/client/.vite/manifest.json +6 -6
  131. package/dist/client/__vitest__/assets/index-Da0hb3oU.css +1 -0
  132. package/dist/client/__vitest__/assets/index-Di71CKDo.js +63 -0
  133. package/dist/client/__vitest__/favicon.ico +0 -0
  134. package/dist/client/__vitest__/favicon.svg +49 -4
  135. package/dist/client/__vitest__/index.html +2 -2
  136. package/dist/client/__vitest_browser__/{orchestrator-S_3e_uzt.js → orchestrator-CXs6qrFe.js} +70 -28
  137. package/dist/client/__vitest_browser__/{tester-k74mgIRa.js → tester-K5NNxh1O.js} +167 -58
  138. package/dist/client/__vitest_browser__/{utils-uxqdqUz8.js → utils-C2ISqq1C.js} +2 -2
  139. package/dist/client/favicon.svg +49 -4
  140. package/dist/client/orchestrator.html +2 -2
  141. package/dist/client/tester/tester.html +2 -2
  142. package/dist/client.js +20 -13
  143. package/dist/config.cjs +3 -2
  144. package/dist/config.d.ts +13 -12
  145. package/dist/config.js +2 -2
  146. package/dist/context.js +108 -22
  147. package/dist/coverage.d.ts +12 -8
  148. package/dist/coverage.js +8 -5
  149. package/dist/environments.js +3 -1
  150. package/dist/expect-element.js +23 -23
  151. package/dist/index-5Pe7X7sp.js +7 -0
  152. package/dist/index.d.ts +66 -27
  153. package/dist/index.js +10 -9
  154. package/dist/locators.d.ts +14 -3
  155. package/dist/locators.js +1 -1
  156. package/dist/module-evaluator.d.ts +11 -1
  157. package/dist/module-evaluator.js +43 -26
  158. package/dist/node.d.ts +28 -14
  159. package/dist/node.js +42 -40
  160. package/dist/nodejs-worker-loader.js +41 -0
  161. package/dist/plugins/mocker-transforms.mjs +2 -0
  162. package/dist/plugins/utils-source-map-node.mjs +2 -0
  163. package/dist/reporters.d.ts +8 -8
  164. package/dist/reporters.js +7 -5
  165. package/dist/runners.d.ts +24 -5
  166. package/dist/runners.js +6 -6
  167. package/dist/runtime.d.ts +6 -0
  168. package/dist/runtime.js +35 -0
  169. package/dist/snapshot.js +4 -2
  170. package/dist/suite.d.ts +1 -1
  171. package/dist/suite.js +4 -2
  172. package/dist/vendor/blazediff_core.d.mts +1 -0
  173. package/dist/vendor/blazediff_core.mjs +117 -0
  174. package/dist/vendor/chai.mjs +4 -249
  175. package/dist/vendor/convert-source-map.d.mts +1 -0
  176. package/dist/vendor/convert-source-map.mjs +150 -0
  177. package/dist/vendor/expect-type.d.mts +14 -7
  178. package/dist/vendor/expect-type.mjs +5 -5
  179. package/dist/vendor/std-env.d.mts +131 -40
  180. package/dist/vendor/std-env.mjs +114 -117
  181. package/dist/worker.d.ts +6 -6
  182. package/dist/worker.js +27 -21
  183. package/dist/workers/forks.js +23 -17
  184. package/dist/workers/runVmTests.js +18 -16
  185. package/dist/workers/threads.js +23 -17
  186. package/dist/workers/vmForks.js +15 -12
  187. package/dist/workers/vmThreads.js +15 -12
  188. package/globals.d.ts +2 -0
  189. package/package.json +36 -27
  190. package/suppress-warnings.cjs +1 -0
  191. package/dist/@vitest/browser/client/__vitest__/assets/index-BUCFJtth.js +0 -57
  192. package/dist/@vitest/browser/client/__vitest__/assets/index-DlhE0rqZ.css +0 -1
  193. package/dist/@vitest/browser/index-D6m36C6U.js +0 -11
  194. package/dist/@vitest/utils/chunk-_commonjsHelpers.js +0 -5
  195. package/dist/@vitest/utils/highlight.d.ts +0 -9
  196. package/dist/@vitest/utils/highlight.js +0 -538
  197. package/dist/chunks/date.Bq6ZW5rf.js +0 -73
  198. package/dist/chunks/rpc.BoxB0q7B.js +0 -76
  199. package/dist/chunks/test.B8ej_ZHS.js +0 -254
  200. package/dist/client/__vitest__/assets/index-BUCFJtth.js +0 -57
  201. package/dist/client/__vitest__/assets/index-DlhE0rqZ.css +0 -1
  202. package/dist/index-D6m36C6U.js +0 -6
  203. package/dist/mocker.d.ts +0 -1
  204. package/dist/mocker.js +0 -1
  205. package/dist/module-runner.js +0 -17
@@ -1,8 +1,8 @@
1
1
  import { processError } from '../utils/error.js';
2
- import { isObject, createDefer, assertTypes, toArray, isNegativeNaN, objectAttr, shuffle } from '../utils/helpers.js';
2
+ import { isObject, filterOutComments, ordinal, createDefer, assertTypes, toArray, isNegativeNaN, unique, objectAttr, shuffle } from '../utils/helpers.js';
3
3
  import { getSafeTimers } from '../utils/timers.js';
4
4
  import { format, formatRegExp, objDisplay } from '../utils/display.js';
5
- import { c as createChainable, e as createTaskName, f as findTestFileStackTrace, b as createFileTask, a as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, l as limitConcurrency, p as partitionSuiteChildren, r as hasTests, q as hasFailed } from './chunk-tasks.js';
5
+ import { w as getChainableContext, a as createChainable, v as validateTags, e as createTaskName, x as createNoTagsError, f as findTestFileStackTrace, d as createTagsFilter, b as createFileTask, c as calculateSuiteHash, u as someTasksAreOnly, q as interpretTaskModes, s as limitConcurrency, t as partitionSuiteChildren, p as hasTests, o as hasFailed } from './chunk-tasks.js';
6
6
  import '../utils/source-map.js';
7
7
  import '../../vendor/pathe.mjs';
8
8
 
@@ -24,6 +24,24 @@ class TestRunAbortError extends Error {
24
24
  this.reason = reason;
25
25
  }
26
26
  }
27
+ class FixtureDependencyError extends Error {
28
+ name = "FixtureDependencyError";
29
+ }
30
+ class FixtureAccessError extends Error {
31
+ name = "FixtureAccessError";
32
+ }
33
+ class FixtureParseError extends Error {
34
+ name = "FixtureParseError";
35
+ }
36
+ class AroundHookSetupError extends Error {
37
+ name = "AroundHookSetupError";
38
+ }
39
+ class AroundHookTeardownError extends Error {
40
+ name = "AroundHookTeardownError";
41
+ }
42
+ class AroundHookMultipleCallsError extends Error {
43
+ name = "AroundHookMultipleCallsError";
44
+ }
27
45
 
28
46
  // use WeakMap here to make the Test and Suite object serializable
29
47
  const fnMap = new WeakMap();
@@ -38,7 +56,7 @@ function getFn(key) {
38
56
  function setTestFixture(key, fixture) {
39
57
  testFixtureMap.set(key, fixture);
40
58
  }
41
- function getTestFixture(key) {
59
+ function getTestFixtures(key) {
42
60
  return testFixtureMap.get(key);
43
61
  }
44
62
  function setHooks(key, hooks) {
@@ -48,85 +66,182 @@ function getHooks(key) {
48
66
  return hooksMap.get(key);
49
67
  }
50
68
 
51
- function mergeScopedFixtures(testFixtures, scopedFixtures) {
52
- const scopedFixturesMap = scopedFixtures.reduce((map, fixture) => {
53
- map[fixture.prop] = fixture;
54
- return map;
55
- }, {});
56
- const newFixtures = {};
57
- testFixtures.forEach((fixture) => {
58
- const useFixture = scopedFixturesMap[fixture.prop] || { ...fixture };
59
- newFixtures[useFixture.prop] = useFixture;
60
- });
61
- for (const fixtureKep in newFixtures) {
62
- var _fixture$deps;
63
- const fixture = newFixtures[fixtureKep];
64
- // if the fixture was define before the scope, then its dep
65
- // will reference the original fixture instead of the scope
66
- fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep) => newFixtures[dep.prop]);
67
- }
68
- return Object.values(newFixtures);
69
- }
70
- function mergeContextFixtures(fixtures, context, runner) {
71
- const fixtureOptionKeys = [
69
+ class TestFixtures {
70
+ _suiteContexts;
71
+ _overrides = new WeakMap();
72
+ _registrations;
73
+ static _definitions = [];
74
+ static _builtinFixtures = [
75
+ "task",
76
+ "signal",
77
+ "onTestFailed",
78
+ "onTestFinished",
79
+ "skip",
80
+ "annotate"
81
+ ];
82
+ static _fixtureOptionKeys = [
72
83
  "auto",
73
84
  "injected",
74
85
  "scope"
75
86
  ];
76
- const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
77
- const fixtureItem = { value };
78
- if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
79
- var _runner$injectValue;
80
- // fixture with options
81
- Object.assign(fixtureItem, value[1]);
82
- const userValue = value[0];
83
- fixtureItem.value = fixtureItem.injected ? ((_runner$injectValue = runner.injectValue) === null || _runner$injectValue === void 0 ? void 0 : _runner$injectValue.call(runner, prop)) ?? userValue : userValue;
84
- }
85
- fixtureItem.scope = fixtureItem.scope || "test";
86
- if (fixtureItem.scope === "worker" && !runner.getWorkerContext) {
87
- fixtureItem.scope = "file";
88
- }
89
- fixtureItem.prop = prop;
90
- fixtureItem.isFn = typeof fixtureItem.value === "function";
91
- return fixtureItem;
92
- });
93
- if (Array.isArray(context.fixtures)) {
94
- context.fixtures = context.fixtures.concat(fixtureArray);
95
- } else {
96
- context.fixtures = fixtureArray;
97
- }
98
- // Update dependencies of fixture functions
99
- fixtureArray.forEach((fixture) => {
100
- if (fixture.isFn) {
101
- const usedProps = getUsedProps(fixture.value);
102
- if (usedProps.length) {
103
- fixture.deps = context.fixtures.filter(({ prop }) => prop !== fixture.prop && usedProps.includes(prop));
87
+ static _fixtureScopes = [
88
+ "test",
89
+ "file",
90
+ "worker"
91
+ ];
92
+ static _workerContextSuite = { type: "worker" };
93
+ static clearDefinitions() {
94
+ TestFixtures._definitions.length = 0;
95
+ }
96
+ static getWorkerContexts() {
97
+ return TestFixtures._definitions.map((f) => f.getWorkerContext());
98
+ }
99
+ static getFileContexts(file) {
100
+ return TestFixtures._definitions.map((f) => f.getFileContext(file));
101
+ }
102
+ constructor(registrations) {
103
+ this._registrations = registrations ?? new Map();
104
+ this._suiteContexts = new WeakMap();
105
+ TestFixtures._definitions.push(this);
106
+ }
107
+ extend(runner, userFixtures) {
108
+ const { suite } = getCurrentSuite();
109
+ const isTopLevel = !suite || suite.file === suite;
110
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel);
111
+ return new TestFixtures(registrations);
112
+ }
113
+ get(suite) {
114
+ let currentSuite = suite;
115
+ while (currentSuite) {
116
+ const overrides = this._overrides.get(currentSuite);
117
+ // return the closest override
118
+ if (overrides) {
119
+ return overrides;
104
120
  }
105
- // test can access anything, so we ignore it
106
- if (fixture.scope !== "test") {
107
- var _fixture$deps2;
108
- (_fixture$deps2 = fixture.deps) === null || _fixture$deps2 === void 0 ? void 0 : _fixture$deps2.forEach((dep) => {
109
- if (!dep.isFn) {
110
- // non fn fixtures are always resolved and available to anyone
111
- return;
112
- }
113
- // worker scope can only import from worker scope
114
- if (fixture.scope === "worker" && dep.scope === "worker") {
115
- return;
116
- }
117
- // file scope an import from file and worker scopes
118
- if (fixture.scope === "file" && dep.scope !== "test") {
119
- return;
120
- }
121
- throw new SyntaxError(`cannot use the ${dep.scope} fixture "${dep.prop}" inside the ${fixture.scope} fixture "${fixture.prop}"`);
122
- });
121
+ if (currentSuite === currentSuite.file) {
122
+ break;
123
123
  }
124
+ currentSuite = currentSuite.suite || currentSuite.file;
125
+ }
126
+ return this._registrations;
127
+ }
128
+ override(runner, userFixtures) {
129
+ const { suite: currentSuite, file } = getCurrentSuite();
130
+ const suite = currentSuite || file;
131
+ const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
132
+ // Create a copy of the closest parent's registrations to avoid modifying them
133
+ // For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
134
+ const suiteRegistrations = new Map(this.get(suite));
135
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
136
+ // If defined in top-level, just override all registrations
137
+ // We don't support overriding suite-level fixtures anyway (it will throw an error)
138
+ if (isTopLevel) {
139
+ this._registrations = registrations;
140
+ } else {
141
+ this._overrides.set(suite, registrations);
124
142
  }
125
- });
126
- return context;
143
+ }
144
+ getFileContext(file) {
145
+ if (!this._suiteContexts.has(file)) {
146
+ this._suiteContexts.set(file, Object.create(null));
147
+ }
148
+ return this._suiteContexts.get(file);
149
+ }
150
+ getWorkerContext() {
151
+ if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) {
152
+ this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
153
+ }
154
+ return this._suiteContexts.get(TestFixtures._workerContextSuite);
155
+ }
156
+ parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
157
+ const errors = [];
158
+ Object.entries(userFixtures).forEach(([name, fn]) => {
159
+ let options;
160
+ let value;
161
+ let _options;
162
+ if (Array.isArray(fn) && fn.length >= 2 && isObject(fn[1]) && Object.keys(fn[1]).some((key) => TestFixtures._fixtureOptionKeys.includes(key))) {
163
+ _options = fn[1];
164
+ options = {
165
+ auto: _options.auto ?? false,
166
+ scope: _options.scope ?? "test",
167
+ injected: _options.injected ?? false
168
+ };
169
+ value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
170
+ } else {
171
+ value = fn;
172
+ }
173
+ const parent = registrations.get(name);
174
+ if (parent && options) {
175
+ if (parent.scope !== options.scope) {
176
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
177
+ }
178
+ if (parent.auto !== options.auto) {
179
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
180
+ }
181
+ } else if (parent) {
182
+ options = {
183
+ auto: parent.auto,
184
+ scope: parent.scope,
185
+ injected: parent.injected
186
+ };
187
+ } else if (!options) {
188
+ options = {
189
+ auto: false,
190
+ injected: false,
191
+ scope: "test"
192
+ };
193
+ }
194
+ if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) {
195
+ errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
196
+ }
197
+ if (!supportNonTest && options.scope !== "test") {
198
+ 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.`));
199
+ }
200
+ const deps = isFixtureFunction(value) ? getUsedProps(value) : new Set();
201
+ const item = {
202
+ name,
203
+ value,
204
+ auto: options.auto ?? false,
205
+ injected: options.injected ?? false,
206
+ scope: options.scope ?? "test",
207
+ deps,
208
+ parent
209
+ };
210
+ registrations.set(name, item);
211
+ if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) {
212
+ item.scope = "file";
213
+ }
214
+ });
215
+ // validate fixture dependency scopes
216
+ for (const fixture of registrations.values()) {
217
+ for (const depName of fixture.deps) {
218
+ if (TestFixtures._builtinFixtures.includes(depName)) {
219
+ continue;
220
+ }
221
+ const dep = registrations.get(depName);
222
+ if (!dep) {
223
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
224
+ continue;
225
+ }
226
+ if (depName === fixture.name && !fixture.parent) {
227
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
228
+ continue;
229
+ }
230
+ if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
231
+ errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
232
+ continue;
233
+ }
234
+ }
235
+ }
236
+ if (errors.length === 1) {
237
+ throw errors[0];
238
+ } else if (errors.length > 1) {
239
+ throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
240
+ }
241
+ return registrations;
242
+ }
127
243
  }
128
- const fixtureValueMaps = new Map();
129
- const cleanupFnArrayMap = new Map();
244
+ const cleanupFnArrayMap = new WeakMap();
130
245
  async function callFixtureCleanup(context) {
131
246
  const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
132
247
  for (const cleanup of cleanupFnArray.reverse()) {
@@ -134,96 +249,152 @@ async function callFixtureCleanup(context) {
134
249
  }
135
250
  cleanupFnArrayMap.delete(context);
136
251
  }
137
- function withFixtures(runner, fn, testContext) {
138
- return (hookContext) => {
139
- const context = hookContext || testContext;
252
+ /**
253
+ * Returns the current number of cleanup functions registered for the context.
254
+ * This can be used as a checkpoint to later clean up only fixtures added after this point.
255
+ */
256
+ function getFixtureCleanupCount(context) {
257
+ return cleanupFnArrayMap.get(context)?.length ?? 0;
258
+ }
259
+ /**
260
+ * Cleans up only fixtures that were added after the given checkpoint index.
261
+ * This is used by aroundEach to clean up fixtures created inside runTest()
262
+ * while preserving fixtures that were created for aroundEach itself.
263
+ */
264
+ async function callFixtureCleanupFrom(context, fromIndex) {
265
+ const cleanupFnArray = cleanupFnArrayMap.get(context);
266
+ if (!cleanupFnArray || cleanupFnArray.length <= fromIndex) {
267
+ return;
268
+ }
269
+ // Get items added after the checkpoint
270
+ const toCleanup = cleanupFnArray.slice(fromIndex);
271
+ // Clean up in reverse order
272
+ for (const cleanup of toCleanup.reverse()) {
273
+ await cleanup();
274
+ }
275
+ // Remove cleaned up items from the array, keeping items before checkpoint
276
+ cleanupFnArray.length = fromIndex;
277
+ }
278
+ const contextHasFixturesCache = new WeakMap();
279
+ function withFixtures(fn, options) {
280
+ const collector = getCurrentSuite();
281
+ const suite = options?.suite || collector.suite || collector.file;
282
+ return async (hookContext) => {
283
+ const context = hookContext || options?.context;
140
284
  if (!context) {
285
+ if (options?.suiteHook) {
286
+ validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
287
+ }
141
288
  return fn({});
142
289
  }
143
- const fixtures = getTestFixture(context);
144
- if (!(fixtures === null || fixtures === void 0 ? void 0 : fixtures.length)) {
290
+ const fixtures = options?.fixtures || getTestFixtures(context);
291
+ if (!fixtures) {
145
292
  return fn(context);
146
293
  }
147
- const usedProps = getUsedProps(fn);
148
- const hasAutoFixture = fixtures.some(({ auto }) => auto);
149
- if (!usedProps.length && !hasAutoFixture) {
294
+ const registrations = fixtures.get(suite);
295
+ if (!registrations.size) {
150
296
  return fn(context);
151
297
  }
152
- if (!fixtureValueMaps.get(context)) {
153
- fixtureValueMaps.set(context, new Map());
298
+ const usedFixtures = [];
299
+ const usedProps = getUsedProps(fn);
300
+ for (const fixture of registrations.values()) {
301
+ if (fixture.auto || usedProps.has(fixture.name)) {
302
+ usedFixtures.push(fixture);
303
+ }
304
+ }
305
+ if (!usedFixtures.length) {
306
+ return fn(context);
154
307
  }
155
- const fixtureValueMap = fixtureValueMaps.get(context);
156
308
  if (!cleanupFnArrayMap.has(context)) {
157
309
  cleanupFnArrayMap.set(context, []);
158
310
  }
159
311
  const cleanupFnArray = cleanupFnArrayMap.get(context);
160
- const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop));
161
- const pendingFixtures = resolveDeps(usedFixtures);
312
+ const pendingFixtures = resolveDeps(usedFixtures, registrations);
162
313
  if (!pendingFixtures.length) {
163
314
  return fn(context);
164
315
  }
165
- async function resolveFixtures() {
166
- for (const fixture of pendingFixtures) {
316
+ // Check if suite-level hook is trying to access test-scoped fixtures
317
+ // Suite hooks (beforeAll/afterAll/aroundAll) can only access file/worker scoped fixtures
318
+ if (options?.suiteHook) {
319
+ const testScopedFixtures = pendingFixtures.filter((f) => f.scope === "test");
320
+ if (testScopedFixtures.length > 0) {
321
+ const fixtureNames = testScopedFixtures.map((f) => `"${f.name}"`).join(", ");
322
+ const alternativeHook = {
323
+ aroundAll: "aroundEach",
324
+ beforeAll: "beforeEach",
325
+ afterAll: "afterEach"
326
+ };
327
+ 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 ${alternativeHook[options.suiteHook]} hook.`);
328
+ // Use stack trace from hook registration for better error location
329
+ if (options.stackTraceError?.stack) {
330
+ error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
331
+ }
332
+ throw error;
333
+ }
334
+ }
335
+ if (!contextHasFixturesCache.has(context)) {
336
+ contextHasFixturesCache.set(context, new WeakSet());
337
+ }
338
+ const cachedFixtures = contextHasFixturesCache.get(context);
339
+ for (const fixture of pendingFixtures) {
340
+ if (fixture.scope === "test") {
167
341
  // fixture could be already initialized during "before" hook
168
- if (fixtureValueMap.has(fixture)) {
342
+ // we can't check "fixture.name" in context because context may
343
+ // access the parent fixture ({ a: ({ a }) => {} })
344
+ if (cachedFixtures.has(fixture)) {
169
345
  continue;
170
346
  }
171
- const resolvedValue = await resolveFixtureValue(runner, fixture, context, cleanupFnArray);
172
- context[fixture.prop] = resolvedValue;
173
- fixtureValueMap.set(fixture, resolvedValue);
174
- if (fixture.scope === "test") {
175
- cleanupFnArray.unshift(() => {
176
- fixtureValueMap.delete(fixture);
177
- });
178
- }
347
+ cachedFixtures.add(fixture);
348
+ const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
349
+ context[fixture.name] = resolvedValue;
350
+ cleanupFnArray.push(() => {
351
+ cachedFixtures.delete(fixture);
352
+ });
353
+ } else {
354
+ const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
355
+ context[fixture.name] = resolvedValue;
179
356
  }
180
357
  }
181
- return resolveFixtures().then(() => fn(context));
358
+ return fn(context);
182
359
  };
183
360
  }
184
- const globalFixturePromise = new WeakMap();
185
- function resolveFixtureValue(runner, fixture, context, cleanupFnArray) {
186
- var _runner$getWorkerCont;
187
- const fileContext = getFileContext(context.task.file);
188
- const workerContext = (_runner$getWorkerCont = runner.getWorkerContext) === null || _runner$getWorkerCont === void 0 ? void 0 : _runner$getWorkerCont.call(runner);
189
- if (!fixture.isFn) {
190
- var _fixture$prop;
191
- fileContext[_fixture$prop = fixture.prop] ?? (fileContext[_fixture$prop] = fixture.value);
192
- if (workerContext) {
193
- var _fixture$prop2;
194
- workerContext[_fixture$prop2 = fixture.prop] ?? (workerContext[_fixture$prop2] = fixture.value);
195
- }
361
+ function isFixtureFunction(value) {
362
+ return typeof value === "function";
363
+ }
364
+ function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
365
+ if (!isFixtureFunction(fixture.value)) {
196
366
  return fixture.value;
197
367
  }
198
- if (fixture.scope === "test") {
199
- return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
200
- }
201
- // in case the test runs in parallel
202
- if (globalFixturePromise.has(fixture)) {
203
- return globalFixturePromise.get(fixture);
368
+ return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
369
+ }
370
+ const scopedFixturePromiseCache = new WeakMap();
371
+ async function resolveScopeFixtureValue(fixtures, suite, fixture) {
372
+ const workerContext = fixtures.getWorkerContext();
373
+ const fileContext = fixtures.getFileContext(suite.file);
374
+ const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
375
+ if (!isFixtureFunction(fixture.value)) {
376
+ fixtureContext[fixture.name] = fixture.value;
377
+ return fixture.value;
204
378
  }
205
- let fixtureContext;
206
- if (fixture.scope === "worker") {
207
- if (!workerContext) {
208
- throw new TypeError("[@vitest/runner] The worker context is not available in the current test runner. Please, provide the `getWorkerContext` method when initiating the runner.");
209
- }
210
- fixtureContext = workerContext;
211
- } else {
212
- fixtureContext = fileContext;
379
+ if (fixture.name in fixtureContext) {
380
+ return fixtureContext[fixture.name];
213
381
  }
214
- if (fixture.prop in fixtureContext) {
215
- return fixtureContext[fixture.prop];
382
+ if (scopedFixturePromiseCache.has(fixture)) {
383
+ return scopedFixturePromiseCache.get(fixture);
216
384
  }
217
385
  if (!cleanupFnArrayMap.has(fixtureContext)) {
218
386
  cleanupFnArrayMap.set(fixtureContext, []);
219
387
  }
220
388
  const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
221
- const promise = resolveFixtureFunction(fixture.value, fixtureContext, cleanupFnFileArray).then((value) => {
222
- fixtureContext[fixture.prop] = value;
223
- globalFixturePromise.delete(fixture);
389
+ const promise = resolveFixtureFunction(fixture.value, fixture.scope === "file" ? {
390
+ ...workerContext,
391
+ ...fileContext
392
+ } : fixtureContext, cleanupFnFileArray).then((value) => {
393
+ fixtureContext[fixture.name] = value;
394
+ scopedFixturePromiseCache.delete(fixture);
224
395
  return value;
225
396
  });
226
- globalFixturePromise.set(fixture, promise);
397
+ scopedFixturePromiseCache.set(fixture, promise);
227
398
  return promise;
228
399
  }
229
400
  async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
@@ -254,27 +425,60 @@ async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
254
425
  });
255
426
  return useFnArgPromise;
256
427
  }
257
- function resolveDeps(fixtures, depSet = new Set(), pendingFixtures = []) {
258
- fixtures.forEach((fixture) => {
428
+ function resolveDeps(usedFixtures, registrations, depSet = new Set(), pendingFixtures = []) {
429
+ usedFixtures.forEach((fixture) => {
259
430
  if (pendingFixtures.includes(fixture)) {
260
431
  return;
261
432
  }
262
- if (!fixture.isFn || !fixture.deps) {
433
+ if (!isFixtureFunction(fixture.value) || !fixture.deps) {
263
434
  pendingFixtures.push(fixture);
264
435
  return;
265
436
  }
266
437
  if (depSet.has(fixture)) {
267
- throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`);
438
+ if (fixture.parent) {
439
+ fixture = fixture.parent;
440
+ } else {
441
+ throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map((d) => d.name).join(" <- ")}`);
442
+ }
268
443
  }
269
444
  depSet.add(fixture);
270
- resolveDeps(fixture.deps, depSet, pendingFixtures);
445
+ resolveDeps([...fixture.deps].map((n) => n === fixture.name ? fixture.parent : registrations.get(n)).filter((n) => !!n), registrations, depSet, pendingFixtures);
271
446
  pendingFixtures.push(fixture);
272
447
  depSet.clear();
273
448
  });
274
449
  return pendingFixtures;
275
450
  }
276
- function getUsedProps(fn) {
277
- let fnString = filterOutComments(fn.toString());
451
+ function validateSuiteHook(fn, hook, suiteError) {
452
+ const usedProps = getUsedProps(fn, {
453
+ sourceError: suiteError,
454
+ suiteHook: hook
455
+ });
456
+ if (usedProps.size) {
457
+ 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}()"?\n` + `If 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`);
458
+ if (suiteError) {
459
+ error.stack = suiteError.stack?.replace(suiteError.message, error.message);
460
+ }
461
+ throw error;
462
+ }
463
+ }
464
+ const kPropsSymbol = Symbol("$vitest:fixture-props");
465
+ const kPropNamesSymbol = Symbol("$vitest:fixture-prop-names");
466
+ function configureProps(fn, options) {
467
+ Object.defineProperty(fn, kPropsSymbol, {
468
+ value: options,
469
+ enumerable: false
470
+ });
471
+ }
472
+ function memoProps(fn, props) {
473
+ fn[kPropNamesSymbol] = props;
474
+ return props;
475
+ }
476
+ function getUsedProps(fn, { sourceError, suiteHook } = {}) {
477
+ if (kPropNamesSymbol in fn) {
478
+ return fn[kPropNamesSymbol];
479
+ }
480
+ const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
481
+ let fnString = filterOutComments(implementation.toString());
278
482
  // match lowered async function and strip it off
279
483
  // example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9LAoKICBoMTogYXN5bmMgKCkgPT4ge30sCiAgLy8gY29tbWVudCBiZXR3ZWVuCiAgaDI6IGFzeW5jIChhKSA9PiB7fSwKfQ
280
484
  // __async(this, null, function*
@@ -285,56 +489,37 @@ function getUsedProps(fn) {
285
489
  }
286
490
  const match = fnString.match(/[^(]*\(([^)]*)/);
287
491
  if (!match) {
288
- return [];
492
+ return memoProps(fn, new Set());
289
493
  }
290
494
  const args = splitByComma(match[1]);
291
495
  if (!args.length) {
292
- return [];
496
+ return memoProps(fn, new Set());
293
497
  }
294
- let first = args[0];
295
- if ("__VITEST_FIXTURE_INDEX__" in fn) {
296
- first = args[fn.__VITEST_FIXTURE_INDEX__];
297
- if (!first) {
298
- return [];
299
- }
498
+ const fixturesArgument = args[fixturesIndex];
499
+ if (!fixturesArgument) {
500
+ return memoProps(fn, new Set());
300
501
  }
301
- if (!(first[0] === "{" && first.endsWith("}"))) {
302
- throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
502
+ if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
503
+ const ordinalArgument = ordinal(fixturesIndex + 1);
504
+ 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.` : ""}`);
505
+ if (sourceError) {
506
+ error.stack = sourceError.stack?.replace(sourceError.message, error.message);
507
+ }
508
+ throw error;
303
509
  }
304
- const _first = first.slice(1, -1).replace(/\s/g, "");
510
+ const _first = fixturesArgument.slice(1, -1).replace(/\s/g, "");
305
511
  const props = splitByComma(_first).map((prop) => {
306
512
  return prop.replace(/:.*|=.*/g, "");
307
513
  });
308
514
  const last = props.at(-1);
309
515
  if (last && last.startsWith("...")) {
310
- throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
311
- }
312
- return props;
313
- }
314
- function filterOutComments(s) {
315
- const result = [];
316
- let commentState = "none";
317
- for (let i = 0; i < s.length; ++i) {
318
- if (commentState === "singleline") {
319
- if (s[i] === "\n") {
320
- commentState = "none";
321
- }
322
- } else if (commentState === "multiline") {
323
- if (s[i - 1] === "*" && s[i] === "/") {
324
- commentState = "none";
325
- }
326
- } else if (commentState === "none") {
327
- if (s[i] === "/" && s[i + 1] === "/") {
328
- commentState = "singleline";
329
- } else if (s[i] === "/" && s[i + 1] === "*") {
330
- commentState = "multiline";
331
- i += 2;
332
- } else {
333
- result.push(s[i]);
334
- }
516
+ const error = new FixtureParseError(`Rest parameters are not supported in fixtures, received "${last}".`);
517
+ if (sourceError) {
518
+ error.stack = sourceError.stack?.replace(sourceError.message, error.message);
335
519
  }
520
+ throw error;
336
521
  }
337
- return result.join("");
522
+ return memoProps(fn, new Set(props));
338
523
  }
339
524
  function splitByComma(s) {
340
525
  const result = [];
@@ -383,6 +568,8 @@ function getDefaultHookTimeout() {
383
568
  }
384
569
  const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
385
570
  const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
571
+ const AROUND_TIMEOUT_KEY = Symbol.for("VITEST_AROUND_TIMEOUT");
572
+ const AROUND_STACK_TRACE_KEY = Symbol.for("VITEST_AROUND_STACK_TRACE");
386
573
  function getBeforeHookCleanupCallback(hook, result, context) {
387
574
  if (typeof result === "function") {
388
575
  const timeout = CLEANUP_TIMEOUT_KEY in hook && typeof hook[CLEANUP_TIMEOUT_KEY] === "number" ? hook[CLEANUP_TIMEOUT_KEY] : getDefaultHookTimeout();
@@ -414,7 +601,8 @@ function getBeforeHookCleanupCallback(hook, result, context) {
414
601
  function beforeAll(fn, timeout = getDefaultHookTimeout()) {
415
602
  assertTypes(fn, "\"beforeAll\" callback", ["function"]);
416
603
  const stackTraceError = new Error("STACK_TRACE_ERROR");
417
- return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
604
+ const context = getChainableContext(this);
605
+ return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
418
606
  [CLEANUP_TIMEOUT_KEY]: timeout,
419
607
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
420
608
  }));
@@ -438,7 +626,9 @@ function beforeAll(fn, timeout = getDefaultHookTimeout()) {
438
626
  */
439
627
  function afterAll(fn, timeout) {
440
628
  assertTypes(fn, "\"afterAll\" callback", ["function"]);
441
- return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
629
+ const context = getChainableContext(this);
630
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
631
+ return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
442
632
  }
443
633
  /**
444
634
  * Registers a callback function to be executed before each test within the current suite.
@@ -460,8 +650,11 @@ function afterAll(fn, timeout) {
460
650
  function beforeEach(fn, timeout = getDefaultHookTimeout()) {
461
651
  assertTypes(fn, "\"beforeEach\" callback", ["function"]);
462
652
  const stackTraceError = new Error("STACK_TRACE_ERROR");
463
- const runner = getRunner();
464
- return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
653
+ const wrapper = (context, suite) => {
654
+ const fixtureResolver = withFixtures(fn, { suite });
655
+ return fixtureResolver(context);
656
+ };
657
+ return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
465
658
  [CLEANUP_TIMEOUT_KEY]: timeout,
466
659
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
467
660
  }));
@@ -485,8 +678,11 @@ function beforeEach(fn, timeout = getDefaultHookTimeout()) {
485
678
  */
486
679
  function afterEach(fn, timeout) {
487
680
  assertTypes(fn, "\"afterEach\" callback", ["function"]);
488
- const runner = getRunner();
489
- return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
681
+ const wrapper = (context, suite) => {
682
+ const fixtureResolver = withFixtures(fn, { suite });
683
+ return fixtureResolver(context);
684
+ };
685
+ return getCurrentSuite().on("afterEach", withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
490
686
  }
491
687
  /**
492
688
  * Registers a callback function to be executed when a test fails within the current suite.
@@ -507,7 +703,7 @@ function afterEach(fn, timeout) {
507
703
  * ```
508
704
  */
509
705
  const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
510
- test.onFailed || (test.onFailed = []);
706
+ test.onFailed ||= [];
511
707
  test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
512
708
  });
513
709
  /**
@@ -534,9 +730,116 @@ const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) =>
534
730
  * ```
535
731
  */
536
732
  const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
537
- test.onFinished || (test.onFinished = []);
733
+ test.onFinished ||= [];
538
734
  test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
539
735
  });
736
+ /**
737
+ * Registers a callback function that wraps around all tests within the current suite.
738
+ * The callback receives a `runSuite` function that must be called to run the suite's tests.
739
+ * This hook is useful for scenarios where you need to wrap an entire suite in a context
740
+ * (e.g., starting a server, opening a database connection that all tests share).
741
+ *
742
+ * **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
743
+ * The first registered hook is the outermost wrapper.
744
+ *
745
+ * @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
746
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
747
+ * @returns {void}
748
+ * @example
749
+ * ```ts
750
+ * // Example of using aroundAll to wrap suite in a tracing span
751
+ * aroundAll(async (runSuite) => {
752
+ * await tracer.trace('test-suite', runSuite);
753
+ * });
754
+ * ```
755
+ * @example
756
+ * ```ts
757
+ * // Example of using aroundAll with fixtures
758
+ * aroundAll(async (runSuite, { db }) => {
759
+ * await db.transaction(() => runSuite());
760
+ * });
761
+ * ```
762
+ */
763
+ function aroundAll(fn, timeout) {
764
+ assertTypes(fn, "\"aroundAll\" callback", ["function"]);
765
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
766
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
767
+ const context = getChainableContext(this);
768
+ return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
769
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
770
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
771
+ }));
772
+ }
773
+ /**
774
+ * Registers a callback function that wraps around each test within the current suite.
775
+ * The callback receives a `runTest` function that must be called to run the test.
776
+ * This hook is useful for scenarios where you need to wrap tests in a context (e.g., database transactions).
777
+ *
778
+ * **Note:** When multiple `aroundEach` hooks are registered, they are nested inside each other.
779
+ * The first registered hook is the outermost wrapper.
780
+ *
781
+ * @param {Function} fn - The callback function that wraps the test. Must call `runTest()` to run the test.
782
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
783
+ * @returns {void}
784
+ * @example
785
+ * ```ts
786
+ * // Example of using aroundEach to wrap tests in a database transaction
787
+ * aroundEach(async (runTest) => {
788
+ * await database.transaction(() => runTest());
789
+ * });
790
+ * ```
791
+ * @example
792
+ * ```ts
793
+ * // Example of using aroundEach with fixtures
794
+ * aroundEach(async (runTest, { db }) => {
795
+ * await db.transaction(() => runTest());
796
+ * });
797
+ * ```
798
+ */
799
+ function aroundEach(fn, timeout) {
800
+ assertTypes(fn, "\"aroundEach\" callback", ["function"]);
801
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
802
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
803
+ const wrapper = (runTest, context, suite) => {
804
+ const innerFn = (ctx) => fn(runTest, ctx, suite);
805
+ configureProps(innerFn, {
806
+ index: 1,
807
+ original: fn
808
+ });
809
+ const fixtureResolver = withFixtures(innerFn, { suite });
810
+ return fixtureResolver(context);
811
+ };
812
+ return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
813
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
814
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
815
+ }));
816
+ }
817
+ function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
818
+ return (...args) => {
819
+ const suite = args.at(-1);
820
+ const prefix = args.slice(0, -1);
821
+ const wrapper = (ctx) => fn(...prefix, ctx, suite);
822
+ configureProps(wrapper, {
823
+ index: contextIndex,
824
+ original: fn
825
+ });
826
+ const fixtures = context?.getFixtures();
827
+ const fileContext = fixtures?.getFileContext(suite.file);
828
+ const fixtured = withFixtures(wrapper, {
829
+ suiteHook,
830
+ fixtures,
831
+ context: fileContext,
832
+ stackTraceError
833
+ });
834
+ return fixtured();
835
+ };
836
+ }
837
+ function getAroundHookTimeout(hook) {
838
+ return AROUND_TIMEOUT_KEY in hook && typeof hook[AROUND_TIMEOUT_KEY] === "number" ? hook[AROUND_TIMEOUT_KEY] : getDefaultHookTimeout();
839
+ }
840
+ function getAroundHookStackTrace(hook) {
841
+ return AROUND_STACK_TRACE_KEY in hook && hook[AROUND_STACK_TRACE_KEY] instanceof Error ? hook[AROUND_STACK_TRACE_KEY] : undefined;
842
+ }
540
843
  function createTestHook(name, handler) {
541
844
  return (fn, timeout) => {
542
845
  assertTypes(fn, `"${name}" callback`, ["function"]);
@@ -693,18 +996,22 @@ function getRunner() {
693
996
  }
694
997
  function createDefaultSuite(runner) {
695
998
  const config = runner.config.sequence;
696
- const collector = suite("", { concurrent: config.concurrent }, () => {});
999
+ const options = {};
1000
+ if (config.concurrent != null) {
1001
+ options.concurrent = config.concurrent;
1002
+ }
1003
+ const collector = suite("", options, () => {});
697
1004
  // no parent suite for top-level tests
698
1005
  delete collector.suite;
699
1006
  return collector;
700
1007
  }
701
1008
  function clearCollectorContext(file, currentRunner) {
1009
+ currentTestFilepath = file.filepath;
1010
+ runner = currentRunner;
702
1011
  if (!defaultSuite) {
703
1012
  defaultSuite = createDefaultSuite(currentRunner);
704
1013
  }
705
1014
  defaultSuite.file = file;
706
- runner = currentRunner;
707
- currentTestFilepath = file.filepath;
708
1015
  collectorContext.tasks.length = 0;
709
1016
  defaultSuite.clear();
710
1017
  collectorContext.currentSuite = defaultSuite;
@@ -719,9 +1026,12 @@ function createSuiteHooks() {
719
1026
  beforeAll: [],
720
1027
  afterAll: [],
721
1028
  beforeEach: [],
722
- afterEach: []
1029
+ afterEach: [],
1030
+ aroundEach: [],
1031
+ aroundAll: []
723
1032
  };
724
1033
  }
1034
+ const POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
725
1035
  function parseArguments(optionsOrFn, timeoutOrTest) {
726
1036
  if (timeoutOrTest != null && typeof timeoutOrTest === "object") {
727
1037
  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.`);
@@ -748,32 +1058,66 @@ function parseArguments(optionsOrFn, timeoutOrTest) {
748
1058
  };
749
1059
  }
750
1060
  // implementations
751
- function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions, parentCollectorFixtures) {
1061
+ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
752
1062
  const tasks = [];
753
1063
  let suite;
754
1064
  initSuite(true);
755
1065
  const task = function(name = "", options = {}) {
756
- var _collectorContext$cur, _collectorContext$cur2, _collectorContext$cur3;
757
- const timeout = (options === null || options === void 0 ? void 0 : options.timeout) ?? runner.config.testTimeout;
758
- const currentSuite = (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.suite;
1066
+ const currentSuite = collectorContext.currentSuite?.suite;
1067
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
1068
+ const parentTags = parentTask?.tags || [];
1069
+ const testTags = unique([...parentTags, ...toArray(options.tags)]);
1070
+ const tagsOptions = testTags.map((tag) => {
1071
+ const tagDefinition = runner.config.tags?.find((t) => t.name === tag);
1072
+ if (!tagDefinition && runner.config.strictTags) {
1073
+ throw createNoTagsError(runner.config.tags, tag);
1074
+ }
1075
+ return tagDefinition;
1076
+ }).filter((r) => r != null).sort((tag1, tag2) => (tag2.priority ?? POSITIVE_INFINITY) - (tag1.priority ?? POSITIVE_INFINITY)).reduce((acc, tag) => {
1077
+ const { name, description, priority, meta, ...options } = tag;
1078
+ Object.assign(acc, options);
1079
+ if (meta) {
1080
+ acc.meta = Object.assign(acc.meta ?? Object.create(null), meta);
1081
+ }
1082
+ return acc;
1083
+ }, {});
1084
+ const testOwnMeta = options.meta;
1085
+ options = {
1086
+ ...tagsOptions,
1087
+ ...options
1088
+ };
1089
+ const timeout = options.timeout ?? runner.config.testTimeout;
1090
+ const parentMeta = currentSuite?.meta;
1091
+ const tagMeta = tagsOptions.meta;
1092
+ const testMeta = Object.create(null);
1093
+ if (tagMeta) {
1094
+ Object.assign(testMeta, tagMeta);
1095
+ }
1096
+ if (parentMeta) {
1097
+ Object.assign(testMeta, parentMeta);
1098
+ }
1099
+ if (testOwnMeta) {
1100
+ Object.assign(testMeta, testOwnMeta);
1101
+ }
759
1102
  const task = {
760
1103
  id: "",
761
1104
  name,
762
- fullName: createTaskName([(currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur2 = collectorContext.currentSuite) === null || _collectorContext$cur2 === void 0 || (_collectorContext$cur2 = _collectorContext$cur2.file) === null || _collectorContext$cur2 === void 0 ? void 0 : _collectorContext$cur2.fullName), name]),
763
- fullTestName: createTaskName([currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName, name]),
1105
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1106
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
764
1107
  suite: currentSuite,
765
1108
  each: options.each,
766
1109
  fails: options.fails,
767
1110
  context: undefined,
768
1111
  type: "test",
769
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur3 = collectorContext.currentSuite) === null || _collectorContext$cur3 === void 0 ? void 0 : _collectorContext$cur3.file),
1112
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
770
1113
  timeout,
771
1114
  retry: options.retry ?? runner.config.retry,
772
1115
  repeats: options.repeats,
773
1116
  mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
774
- meta: options.meta ?? Object.create(null),
1117
+ meta: testMeta,
775
1118
  annotations: [],
776
- artifacts: []
1119
+ artifacts: [],
1120
+ tags: testTags
777
1121
  };
778
1122
  const handler = options.handler;
779
1123
  if (task.mode === "run" && !handler) {
@@ -782,21 +1126,20 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
782
1126
  if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
783
1127
  task.concurrent = true;
784
1128
  }
785
- task.shuffle = suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle;
1129
+ task.shuffle = suiteOptions?.shuffle;
786
1130
  const context = createTestContext(task, runner);
787
1131
  // create test context
788
1132
  Object.defineProperty(task, "context", {
789
1133
  value: context,
790
1134
  enumerable: false
791
1135
  });
792
- setTestFixture(context, options.fixtures);
793
- // custom can be called from any place, let's assume the limit is 15 stacks
1136
+ setTestFixture(context, options.fixtures ?? new TestFixtures());
794
1137
  const limit = Error.stackTraceLimit;
795
- Error.stackTraceLimit = 15;
1138
+ Error.stackTraceLimit = 10;
796
1139
  const stackTraceError = new Error("STACK_TRACE_ERROR");
797
1140
  Error.stackTraceLimit = limit;
798
1141
  if (handler) {
799
- setFn(task, withTimeout(withAwaitAsyncAssertions(withFixtures(runner, handler, context), task), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
1142
+ setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, { context }), task), task.context.signal), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
800
1143
  }
801
1144
  if (runner.config.includeTaskLocation) {
802
1145
  const error = stackTraceError.stack;
@@ -818,8 +1161,14 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
818
1161
  options = Object.assign({}, suiteOptions, options);
819
1162
  }
820
1163
  // inherit concurrent / sequential from suite
821
- options.concurrent = this.concurrent || !this.sequential && (options === null || options === void 0 ? void 0 : options.concurrent);
822
- options.sequential = this.sequential || !this.concurrent && (options === null || options === void 0 ? void 0 : options.sequential);
1164
+ const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent);
1165
+ if (options.concurrent != null && concurrent != null) {
1166
+ options.concurrent = concurrent;
1167
+ }
1168
+ const sequential = this.sequential ?? (!this.concurrent && options?.sequential);
1169
+ if (options.sequential != null && sequential != null) {
1170
+ options.sequential = sequential;
1171
+ }
823
1172
  const test = task(formatName(name), {
824
1173
  ...this,
825
1174
  ...options,
@@ -827,7 +1176,6 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
827
1176
  });
828
1177
  test.type = "test";
829
1178
  });
830
- let collectorFixtures = parentCollectorFixtures;
831
1179
  const collector = {
832
1180
  type: "collector",
833
1181
  name,
@@ -835,44 +1183,39 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
835
1183
  suite,
836
1184
  options: suiteOptions,
837
1185
  test,
1186
+ file: suite.file,
838
1187
  tasks,
839
1188
  collect,
840
1189
  task,
841
1190
  clear,
842
- on: addHook,
843
- fixtures() {
844
- return collectorFixtures;
845
- },
846
- scoped(fixtures) {
847
- const parsed = mergeContextFixtures(fixtures, { fixtures: collectorFixtures }, runner);
848
- if (parsed.fixtures) {
849
- collectorFixtures = parsed.fixtures;
850
- }
851
- }
1191
+ on: addHook
852
1192
  };
853
1193
  function addHook(name, ...fn) {
854
1194
  getHooks(suite)[name].push(...fn);
855
1195
  }
856
1196
  function initSuite(includeLocation) {
857
- var _collectorContext$cur4, _collectorContext$cur5, _collectorContext$cur6;
858
1197
  if (typeof suiteOptions === "number") {
859
1198
  suiteOptions = { timeout: suiteOptions };
860
1199
  }
861
- const currentSuite = (_collectorContext$cur4 = collectorContext.currentSuite) === null || _collectorContext$cur4 === void 0 ? void 0 : _collectorContext$cur4.suite;
1200
+ const currentSuite = collectorContext.currentSuite?.suite;
1201
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
1202
+ const suiteTags = toArray(suiteOptions?.tags);
1203
+ validateTags(runner.config, suiteTags);
862
1204
  suite = {
863
1205
  id: "",
864
1206
  type: "suite",
865
1207
  name,
866
- fullName: createTaskName([(currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur5 = collectorContext.currentSuite) === null || _collectorContext$cur5 === void 0 || (_collectorContext$cur5 = _collectorContext$cur5.file) === null || _collectorContext$cur5 === void 0 ? void 0 : _collectorContext$cur5.fullName), name]),
867
- fullTestName: createTaskName([currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName, name]),
1208
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1209
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
868
1210
  suite: currentSuite,
869
1211
  mode,
870
1212
  each,
871
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur6 = collectorContext.currentSuite) === null || _collectorContext$cur6 === void 0 ? void 0 : _collectorContext$cur6.file),
872
- shuffle: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle,
1213
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
1214
+ shuffle: suiteOptions?.shuffle,
873
1215
  tasks: [],
874
- meta: Object.create(null),
875
- concurrent: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.concurrent
1216
+ meta: suiteOptions?.meta ?? Object.create(null),
1217
+ concurrent: suiteOptions?.concurrent,
1218
+ tags: unique([...parentTask?.tags || [], ...suiteTags])
876
1219
  };
877
1220
  if (runner && includeLocation && runner.config.includeTaskLocation) {
878
1221
  const limit = Error.stackTraceLimit;
@@ -926,34 +1269,46 @@ function withAwaitAsyncAssertions(fn, task) {
926
1269
  }
927
1270
  function createSuite() {
928
1271
  function suiteFn(name, factoryOrOptions, optionsOrFactory) {
929
- var _currentSuite$options;
930
1272
  if (getCurrentTest()) {
931
1273
  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.");
932
1274
  }
933
- let mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
934
1275
  const currentSuite = collectorContext.currentSuite || defaultSuite;
935
1276
  let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
936
- if (mode === "run" && !factory) {
937
- mode = "todo";
938
- }
939
1277
  const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
940
1278
  const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
1279
+ const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
941
1280
  // inherit options from current suite
942
1281
  options = {
943
- ...currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.options,
944
- ...options,
945
- shuffle: this.shuffle ?? options.shuffle ?? (currentSuite === null || currentSuite === void 0 || (_currentSuite$options = currentSuite.options) === null || _currentSuite$options === void 0 ? void 0 : _currentSuite$options.shuffle) ?? (runner === null || runner === void 0 ? void 0 : runner.config.sequence.shuffle)
1282
+ ...parentOptions,
1283
+ ...options
946
1284
  };
1285
+ const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
1286
+ if (shuffle != null) {
1287
+ options.shuffle = shuffle;
1288
+ }
1289
+ let mode = this.only ?? options.only ? "only" : this.skip ?? options.skip ? "skip" : this.todo ?? options.todo ? "todo" : "run";
1290
+ // passed as test(name), assume it's a "todo"
1291
+ if (mode === "run" && !factory) {
1292
+ mode = "todo";
1293
+ }
947
1294
  // inherit concurrent / sequential from suite
948
1295
  const isConcurrent = isConcurrentSpecified || options.concurrent && !isSequentialSpecified;
949
1296
  const isSequential = isSequentialSpecified || options.sequential && !isConcurrentSpecified;
950
- options.concurrent = isConcurrent && !isSequential;
951
- options.sequential = isSequential && !isConcurrent;
952
- return createSuiteCollector(formatName(name), factory, mode, this.each, options, currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fixtures());
1297
+ if (isConcurrent != null) {
1298
+ options.concurrent = isConcurrent && !isSequential;
1299
+ }
1300
+ if (isSequential != null) {
1301
+ options.sequential = isSequential && !isConcurrent;
1302
+ }
1303
+ if (parentMeta) {
1304
+ options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
1305
+ }
1306
+ return createSuiteCollector(formatName(name), factory, mode, this.each, options);
953
1307
  }
954
1308
  suiteFn.each = function(cases, ...args) {
955
- const suite = this.withContext();
956
- this.setContext("each", true);
1309
+ const context = getChainableContext(this);
1310
+ const suite = context.withContext();
1311
+ context.setContext("each", true);
957
1312
  if (Array.isArray(cases) && args.length) {
958
1313
  cases = formatTemplateString(cases, args);
959
1314
  }
@@ -978,7 +1333,7 @@ function createSuite() {
978
1333
  }
979
1334
  }
980
1335
  });
981
- this.setContext("each", undefined);
1336
+ context.setContext("each", undefined);
982
1337
  };
983
1338
  };
984
1339
  suiteFn.for = function(cases, ...args) {
@@ -1004,11 +1359,12 @@ function createSuite() {
1004
1359
  "todo"
1005
1360
  ], suiteFn);
1006
1361
  }
1007
- function createTaskCollector(fn, context) {
1362
+ function createTaskCollector(fn) {
1008
1363
  const taskFn = fn;
1009
1364
  taskFn.each = function(cases, ...args) {
1010
- const test = this.withContext();
1011
- this.setContext("each", true);
1365
+ const context = getChainableContext(this);
1366
+ const test = context.withContext();
1367
+ context.setContext("each", true);
1012
1368
  if (Array.isArray(cases) && args.length) {
1013
1369
  cases = formatTemplateString(cases, args);
1014
1370
  }
@@ -1033,11 +1389,12 @@ function createTaskCollector(fn, context) {
1033
1389
  }
1034
1390
  }
1035
1391
  });
1036
- this.setContext("each", undefined);
1392
+ context.setContext("each", undefined);
1037
1393
  };
1038
1394
  };
1039
1395
  taskFn.for = function(cases, ...args) {
1040
- const test = this.withContext();
1396
+ const context = getChainableContext(this);
1397
+ const test = context.withContext();
1041
1398
  if (Array.isArray(cases) && args.length) {
1042
1399
  cases = formatTemplateString(cases, args);
1043
1400
  }
@@ -1048,8 +1405,10 @@ function createTaskCollector(fn, context) {
1048
1405
  // monkey-patch handler to allow parsing fixture
1049
1406
  const handlerWrapper = handler ? (ctx) => handler(item, ctx) : undefined;
1050
1407
  if (handlerWrapper) {
1051
- handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
1052
- handlerWrapper.toString = () => handler.toString();
1408
+ configureProps(handlerWrapper, {
1409
+ index: 1,
1410
+ original: handler
1411
+ });
1053
1412
  }
1054
1413
  test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
1055
1414
  });
@@ -1061,27 +1420,91 @@ function createTaskCollector(fn, context) {
1061
1420
  taskFn.runIf = function(condition) {
1062
1421
  return condition ? this : this.skip;
1063
1422
  };
1423
+ /**
1424
+ * Parse builder pattern arguments into a fixtures object.
1425
+ * Handles both builder pattern (name, options?, value) and object syntax.
1426
+ */
1427
+ function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
1428
+ // Object syntax: just return as-is
1429
+ if (typeof fixturesOrName !== "string") {
1430
+ return fixturesOrName;
1431
+ }
1432
+ const fixtureName = fixturesOrName;
1433
+ let fixtureOptions;
1434
+ let fixtureValue;
1435
+ if (maybeFn !== undefined) {
1436
+ // (name, options, value) or (name, options, fn)
1437
+ fixtureOptions = optionsOrFn;
1438
+ fixtureValue = maybeFn;
1439
+ } else {
1440
+ // (name, value) or (name, fn)
1441
+ // Check if optionsOrFn looks like fixture options (has scope or auto)
1442
+ if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && ("scope" in optionsOrFn || "auto" in optionsOrFn)) {
1443
+ // (name, options) with no value - treat as empty object fixture
1444
+ fixtureOptions = optionsOrFn;
1445
+ fixtureValue = {};
1446
+ } else {
1447
+ // (name, value) or (name, fn)
1448
+ fixtureOptions = undefined;
1449
+ fixtureValue = optionsOrFn;
1450
+ }
1451
+ }
1452
+ // Function value: wrap with onCleanup pattern
1453
+ if (typeof fixtureValue === "function") {
1454
+ const builderFn = fixtureValue;
1455
+ // Wrap builder pattern function (returns value) to use() pattern
1456
+ const fixture = async (ctx, use) => {
1457
+ let cleanup;
1458
+ const onCleanup = (fn) => {
1459
+ if (cleanup !== undefined) {
1460
+ throw new Error(`onCleanup can only be called once per fixture. ` + `Define separate fixtures if you need multiple cleanup functions.`);
1461
+ }
1462
+ cleanup = fn;
1463
+ };
1464
+ const value = await builderFn(ctx, { onCleanup });
1465
+ await use(value);
1466
+ if (cleanup) {
1467
+ await cleanup();
1468
+ }
1469
+ };
1470
+ configureProps(fixture, { original: builderFn });
1471
+ if (fixtureOptions) {
1472
+ return { [fixtureName]: [fixture, fixtureOptions] };
1473
+ }
1474
+ return { [fixtureName]: fixture };
1475
+ }
1476
+ // Non-function value: use directly
1477
+ if (fixtureOptions) {
1478
+ return { [fixtureName]: [fixtureValue, fixtureOptions] };
1479
+ }
1480
+ return { [fixtureName]: fixtureValue };
1481
+ }
1482
+ taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
1483
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1484
+ getChainableContext(this).getFixtures().override(runner, userFixtures);
1485
+ return this;
1486
+ };
1064
1487
  taskFn.scoped = function(fixtures) {
1065
- const collector = getCurrentSuite();
1066
- collector.scoped(fixtures);
1488
+ console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
1489
+ return this.override(fixtures);
1067
1490
  };
1068
- taskFn.extend = function(fixtures) {
1069
- const _context = mergeContextFixtures(fixtures, context || {}, runner);
1070
- const originalWrapper = fn;
1071
- return createTest(function(name, optionsOrFn, optionsOrTest) {
1072
- const collector = getCurrentSuite();
1073
- const scopedFixtures = collector.fixtures();
1074
- const context = { ...this };
1075
- if (scopedFixtures) {
1076
- context.fixtures = mergeScopedFixtures(context.fixtures || [], scopedFixtures);
1077
- }
1078
- originalWrapper.call(context, formatName(name), optionsOrFn, optionsOrTest);
1079
- }, _context);
1491
+ taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
1492
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1493
+ const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
1494
+ const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
1495
+ fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
1496
+ });
1497
+ getChainableContext(_test).mergeContext({ fixtures });
1498
+ return _test;
1080
1499
  };
1500
+ taskFn.describe = suite;
1501
+ taskFn.suite = suite;
1081
1502
  taskFn.beforeEach = beforeEach;
1082
1503
  taskFn.afterEach = afterEach;
1083
1504
  taskFn.beforeAll = beforeAll;
1084
1505
  taskFn.afterAll = afterAll;
1506
+ taskFn.aroundEach = aroundEach;
1507
+ taskFn.aroundAll = aroundAll;
1085
1508
  const _test = createChainable([
1086
1509
  "concurrent",
1087
1510
  "sequential",
@@ -1089,14 +1512,11 @@ function createTaskCollector(fn, context) {
1089
1512
  "only",
1090
1513
  "todo",
1091
1514
  "fails"
1092
- ], taskFn);
1093
- if (context) {
1094
- _test.mergeContext(context);
1095
- }
1515
+ ], taskFn, { fixtures: new TestFixtures() });
1096
1516
  return _test;
1097
1517
  }
1098
- function createTest(fn, context) {
1099
- return createTaskCollector(fn, context);
1518
+ function createTest(fn) {
1519
+ return createTaskCollector(fn);
1100
1520
  }
1101
1521
  function formatName(name) {
1102
1522
  return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
@@ -1123,14 +1543,13 @@ function formatTitle(template, items, idx) {
1123
1543
  const isObjectItem = isObject(items[0]);
1124
1544
  function formatAttribute(s) {
1125
1545
  return s.replace(/\$([$\w.]+)/g, (_, key) => {
1126
- var _runner$config;
1127
1546
  const isArrayKey = /^\d+$/.test(key);
1128
1547
  if (!isObjectItem && !isArrayKey) {
1129
1548
  return `$${key}`;
1130
1549
  }
1131
1550
  const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
1132
1551
  const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
1133
- return objDisplay(value, { truncate: runner === null || runner === void 0 || (_runner$config = runner.config) === null || _runner$config === void 0 || (_runner$config = _runner$config.chaiConfig) === null || _runner$config === void 0 ? void 0 : _runner$config.truncateThreshold });
1552
+ return objDisplay(value, { truncate: runner?.config?.chaiConfig?.truncateThreshold });
1134
1553
  });
1135
1554
  }
1136
1555
  let output = "";
@@ -1180,14 +1599,13 @@ function formatTemplateString(cases, args) {
1180
1599
  return res;
1181
1600
  }
1182
1601
 
1183
- const now$2 = Date.now;
1602
+ const now$2 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1184
1603
  const collectorContext = {
1185
1604
  tasks: [],
1186
1605
  currentSuite: null
1187
1606
  };
1188
1607
  function collectTask(task) {
1189
- var _collectorContext$cur;
1190
- (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
1608
+ collectorContext.currentSuite?.tasks.push(task);
1191
1609
  }
1192
1610
  async function runWithSuite(suite, fn) {
1193
1611
  const prev = collectorContext.currentSuite;
@@ -1207,16 +1625,15 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
1207
1625
  runner._currentTaskStartTime = startTime;
1208
1626
  runner._currentTaskTimeout = timeout;
1209
1627
  return new Promise((resolve_, reject_) => {
1210
- var _timer$unref;
1211
1628
  const timer = setTimeout(() => {
1212
1629
  clearTimeout(timer);
1213
1630
  rejectTimeoutError();
1214
1631
  }, timeout);
1215
1632
  // `unref` might not exist in browser
1216
- (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
1633
+ timer.unref?.();
1217
1634
  function rejectTimeoutError() {
1218
1635
  const error = makeTimeoutError(isHook, timeout, stackTraceError);
1219
- onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(args, error);
1636
+ onTimeout?.(args, error);
1220
1637
  reject_(error);
1221
1638
  }
1222
1639
  function resolve(result) {
@@ -1256,6 +1673,23 @@ catch (error) {
1256
1673
  });
1257
1674
  });
1258
1675
  }
1676
+ function withCancel(fn, signal) {
1677
+ return (function runWithCancel(...args) {
1678
+ return new Promise((resolve, reject) => {
1679
+ signal.addEventListener("abort", () => reject(signal.reason));
1680
+ try {
1681
+ const result = fn(...args);
1682
+ if (typeof result === "object" && result != null && typeof result.then === "function") {
1683
+ result.then(resolve, reject);
1684
+ } else {
1685
+ resolve(result);
1686
+ }
1687
+ } catch (error) {
1688
+ reject(error);
1689
+ }
1690
+ });
1691
+ });
1692
+ }
1259
1693
  const abortControllers = new WeakMap();
1260
1694
  function abortIfTimeout([context], error) {
1261
1695
  if (context) {
@@ -1264,10 +1698,9 @@ function abortIfTimeout([context], error) {
1264
1698
  }
1265
1699
  function abortContextSignal(context, error) {
1266
1700
  const abortController = abortControllers.get(context);
1267
- abortController === null || abortController === void 0 ? void 0 : abortController.abort(error);
1701
+ abortController?.abort(error);
1268
1702
  }
1269
1703
  function createTestContext(test, runner) {
1270
- var _runner$extendTaskCon;
1271
1704
  const context = function() {
1272
1705
  throw new Error("done() callback is deprecated, use promise instead");
1273
1706
  };
@@ -1283,7 +1716,7 @@ function createTestContext(test, runner) {
1283
1716
  // do nothing
1284
1717
  return undefined;
1285
1718
  }
1286
- test.result ?? (test.result = { state: "skip" });
1719
+ test.result ??= { state: "skip" };
1287
1720
  test.result.pending = true;
1288
1721
  throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
1289
1722
  };
@@ -1314,34 +1747,23 @@ function createTestContext(test, runner) {
1314
1747
  }));
1315
1748
  });
1316
1749
  context.onTestFailed = (handler, timeout) => {
1317
- test.onFailed || (test.onFailed = []);
1750
+ test.onFailed ||= [];
1318
1751
  test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1319
1752
  };
1320
1753
  context.onTestFinished = (handler, timeout) => {
1321
- test.onFinished || (test.onFinished = []);
1754
+ test.onFinished ||= [];
1322
1755
  test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1323
1756
  };
1324
- return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
1757
+ return runner.extendTaskContext?.(context) || context;
1325
1758
  }
1326
1759
  function makeTimeoutError(isHook, timeout, stackTraceError) {
1327
1760
  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"}".`;
1328
1761
  const error = new Error(message);
1329
- if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
1762
+ if (stackTraceError?.stack) {
1330
1763
  error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
1331
1764
  }
1332
1765
  return error;
1333
1766
  }
1334
- const fileContexts = new WeakMap();
1335
- function getFileContext(file) {
1336
- const context = fileContexts.get(file);
1337
- if (!context) {
1338
- throw new Error(`Cannot find file context for ${file.name}`);
1339
- }
1340
- return context;
1341
- }
1342
- function setFileContext(file, context) {
1343
- fileContexts.set(file, context);
1344
- }
1345
1767
 
1346
1768
  async function runSetupFiles(config, files, runner) {
1347
1769
  if (config.sequence.setupFiles === "parallel") {
@@ -1360,18 +1782,22 @@ async function collectTests(specs, runner) {
1360
1782
  const files = [];
1361
1783
  const config = runner.config;
1362
1784
  const $ = runner.trace;
1785
+ let defaultTagsFilter;
1363
1786
  for (const spec of specs) {
1364
1787
  const filepath = typeof spec === "string" ? spec : spec.filepath;
1365
1788
  await $("collect_spec", { "code.file.path": filepath }, async () => {
1366
- var _runner$onCollectStar;
1367
1789
  const testLocations = typeof spec === "string" ? undefined : spec.testLocations;
1790
+ const testNamePattern = typeof spec === "string" ? undefined : spec.testNamePattern;
1791
+ const testIds = typeof spec === "string" ? undefined : spec.testIds;
1792
+ const testTagsFilter = typeof spec === "object" && spec.testTagsFilter ? createTagsFilter(spec.testTagsFilter, config.tags) : undefined;
1793
+ const fileTags = typeof spec === "string" ? [] : spec.fileTags || [];
1368
1794
  const file = createFileTask(filepath, config.root, config.name, runner.pool, runner.viteEnvironment);
1369
- setFileContext(file, Object.create(null));
1795
+ file.tags = fileTags;
1370
1796
  file.shuffle = config.sequence.shuffle;
1371
- (_runner$onCollectStar = runner.onCollectStart) === null || _runner$onCollectStar === void 0 ? void 0 : _runner$onCollectStar.call(runner, file);
1372
- clearCollectorContext(file, runner);
1373
1797
  try {
1374
- var _runner$getImportDura;
1798
+ validateTags(runner.config, fileTags);
1799
+ runner.onCollectStart?.(file);
1800
+ clearCollectorContext(file, runner);
1375
1801
  const setupFiles = toArray(config.setupFiles);
1376
1802
  if (setupFiles.length) {
1377
1803
  const setupStart = now$1();
@@ -1383,7 +1809,7 @@ async function collectTests(specs, runner) {
1383
1809
  }
1384
1810
  const collectStart = now$1();
1385
1811
  await runner.importFile(filepath, "collect");
1386
- const durations = (_runner$getImportDura = runner.getImportDurations) === null || _runner$getImportDura === void 0 ? void 0 : _runner$getImportDura.call(runner);
1812
+ const durations = runner.getImportDurations?.();
1387
1813
  if (durations) {
1388
1814
  file.importDurations = durations;
1389
1815
  }
@@ -1407,20 +1833,22 @@ async function collectTests(specs, runner) {
1407
1833
  setHooks(file, fileHooks);
1408
1834
  file.collectDuration = now$1() - collectStart;
1409
1835
  } catch (e) {
1410
- var _runner$getImportDura2;
1411
- const error = processError(e);
1836
+ const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, runner.config.diffOptions)) : [processError(e, runner.config.diffOptions)];
1412
1837
  file.result = {
1413
1838
  state: "fail",
1414
- errors: [error]
1839
+ errors
1415
1840
  };
1416
- const durations = (_runner$getImportDura2 = runner.getImportDurations) === null || _runner$getImportDura2 === void 0 ? void 0 : _runner$getImportDura2.call(runner);
1841
+ const durations = runner.getImportDurations?.();
1417
1842
  if (durations) {
1418
1843
  file.importDurations = durations;
1419
1844
  }
1420
1845
  }
1421
1846
  calculateSuiteHash(file);
1422
1847
  const hasOnlyTasks = someTasksAreOnly(file);
1423
- interpretTaskModes(file, config.testNamePattern, testLocations, hasOnlyTasks, false, config.allowOnly);
1848
+ if (!testTagsFilter && !defaultTagsFilter && config.tagsFilter) {
1849
+ defaultTagsFilter = createTagsFilter(config.tagsFilter, config.tags);
1850
+ }
1851
+ interpretTaskModes(file, testNamePattern ?? config.testNamePattern, testLocations, testIds, testTagsFilter ?? defaultTagsFilter, hasOnlyTasks, false, config.allowOnly);
1424
1852
  if (file.mode === "queued") {
1425
1853
  file.mode = "run";
1426
1854
  }
@@ -1440,6 +1868,38 @@ function mergeHooks(baseHooks, hooks) {
1440
1868
  const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1441
1869
  const unixNow = Date.now;
1442
1870
  const { clearTimeout, setTimeout } = getSafeTimers();
1871
+ let limitMaxConcurrency;
1872
+ /**
1873
+ * Normalizes retry configuration to extract individual values.
1874
+ * Handles both number and object forms.
1875
+ */
1876
+ function getRetryCount(retry) {
1877
+ if (retry === undefined) {
1878
+ return 0;
1879
+ }
1880
+ if (typeof retry === "number") {
1881
+ return retry;
1882
+ }
1883
+ return retry.count ?? 0;
1884
+ }
1885
+ function getRetryDelay(retry) {
1886
+ if (retry === undefined) {
1887
+ return 0;
1888
+ }
1889
+ if (typeof retry === "number") {
1890
+ return 0;
1891
+ }
1892
+ return retry.delay ?? 0;
1893
+ }
1894
+ function getRetryCondition(retry) {
1895
+ if (retry === undefined) {
1896
+ return undefined;
1897
+ }
1898
+ if (typeof retry === "number") {
1899
+ return undefined;
1900
+ }
1901
+ return retry.condition;
1902
+ }
1443
1903
  function updateSuiteHookState(task, name, state, runner) {
1444
1904
  if (!task.result) {
1445
1905
  task.result = { state: "run" };
@@ -1482,14 +1942,14 @@ async function callTestHooks(runner, test, hooks, sequence) {
1482
1942
  };
1483
1943
  if (sequence === "parallel") {
1484
1944
  try {
1485
- await Promise.all(hooks.map((fn) => fn(test.context)));
1945
+ await Promise.all(hooks.map((fn) => limitMaxConcurrency(() => fn(test.context))));
1486
1946
  } catch (e) {
1487
1947
  failTask(test.result, e, runner.config.diffOptions);
1488
1948
  }
1489
1949
  } else {
1490
1950
  for (const fn of hooks) {
1491
1951
  try {
1492
- await fn(test.context);
1952
+ await limitMaxConcurrency(() => fn(test.context));
1493
1953
  } catch (e) {
1494
1954
  failTask(test.result, e, runner.config.diffOptions);
1495
1955
  }
@@ -1511,7 +1971,9 @@ async function callSuiteHook(suite, currentTask, name, runner, args) {
1511
1971
  updateSuiteHookState(currentTask, name, "run", runner);
1512
1972
  }
1513
1973
  async function runHook(hook) {
1514
- return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : undefined);
1974
+ return limitMaxConcurrency(async () => {
1975
+ return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : undefined);
1976
+ });
1515
1977
  }
1516
1978
  if (sequence === "parallel") {
1517
1979
  callbacks.push(...await Promise.all(hooks.map((hook) => runHook(hook))));
@@ -1528,12 +1990,185 @@ async function callSuiteHook(suite, currentTask, name, runner, args) {
1528
1990
  }
1529
1991
  return callbacks;
1530
1992
  }
1993
+ function getAroundEachHooks(suite) {
1994
+ const hooks = [];
1995
+ const parentSuite = "filepath" in suite ? null : suite.suite || suite.file;
1996
+ if (parentSuite) {
1997
+ hooks.push(...getAroundEachHooks(parentSuite));
1998
+ }
1999
+ hooks.push(...getHooks(suite).aroundEach);
2000
+ return hooks;
2001
+ }
2002
+ function getAroundAllHooks(suite) {
2003
+ return getHooks(suite).aroundAll;
2004
+ }
2005
+ function makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError) {
2006
+ const message = `The ${phase} phase of "${hookName}" hook timed out after ${timeout}ms.`;
2007
+ const ErrorClass = phase === "setup" ? AroundHookSetupError : AroundHookTeardownError;
2008
+ const error = new ErrorClass(message);
2009
+ if (stackTraceError?.stack) {
2010
+ error.stack = stackTraceError.stack.replace(stackTraceError.message, error.message);
2011
+ }
2012
+ return error;
2013
+ }
2014
+ async function callAroundHooks(runInner, options) {
2015
+ const { hooks, hookName, callbackName, onTimeout, invokeHook } = options;
2016
+ if (!hooks.length) {
2017
+ await runInner();
2018
+ return;
2019
+ }
2020
+ const hookErrors = [];
2021
+ const createTimeoutPromise = (timeout, phase, stackTraceError) => {
2022
+ let timer;
2023
+ let timedout = false;
2024
+ const promise = new Promise((_, reject) => {
2025
+ if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) {
2026
+ timer = setTimeout(() => {
2027
+ timedout = true;
2028
+ const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError);
2029
+ onTimeout?.(error);
2030
+ reject(error);
2031
+ }, timeout);
2032
+ timer.unref?.();
2033
+ }
2034
+ });
2035
+ const clear = () => {
2036
+ if (timer) {
2037
+ clearTimeout(timer);
2038
+ timer = undefined;
2039
+ }
2040
+ };
2041
+ return {
2042
+ promise,
2043
+ clear,
2044
+ isTimedOut: () => timedout
2045
+ };
2046
+ };
2047
+ const runNextHook = async (index) => {
2048
+ if (index >= hooks.length) {
2049
+ return runInner();
2050
+ }
2051
+ const hook = hooks[index];
2052
+ const timeout = getAroundHookTimeout(hook);
2053
+ const stackTraceError = getAroundHookStackTrace(hook);
2054
+ let useCalled = false;
2055
+ let setupTimeout;
2056
+ let teardownTimeout;
2057
+ let setupLimitConcurrencyRelease;
2058
+ let teardownLimitConcurrencyRelease;
2059
+ // Promise that resolves when use() is called (setup phase complete)
2060
+ let resolveUseCalled;
2061
+ const useCalledPromise = new Promise((resolve) => {
2062
+ resolveUseCalled = resolve;
2063
+ });
2064
+ // Promise that resolves when use() returns (inner hooks complete, teardown phase starts)
2065
+ let resolveUseReturned;
2066
+ const useReturnedPromise = new Promise((resolve) => {
2067
+ resolveUseReturned = resolve;
2068
+ });
2069
+ // Promise that resolves when hook completes
2070
+ let resolveHookComplete;
2071
+ let rejectHookComplete;
2072
+ const hookCompletePromise = new Promise((resolve, reject) => {
2073
+ resolveHookComplete = resolve;
2074
+ rejectHookComplete = reject;
2075
+ });
2076
+ const use = async () => {
2077
+ // shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.
2078
+ if (setupTimeout.isTimedOut()) {
2079
+ // we can throw any error to bail out.
2080
+ // this error is not seen by end users since `runNextHook` already rejected with timeout error
2081
+ // and this error is caught by `rejectHookComplete`.
2082
+ throw new Error("__VITEST_INTERNAL_AROUND_HOOK_ABORT__");
2083
+ }
2084
+ if (useCalled) {
2085
+ throw new AroundHookMultipleCallsError(`The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. ` + `The callback can only be called once per hook.`);
2086
+ }
2087
+ useCalled = true;
2088
+ resolveUseCalled();
2089
+ // Setup phase completed - clear setup timer
2090
+ setupTimeout.clear();
2091
+ setupLimitConcurrencyRelease?.();
2092
+ // Run inner hooks - don't time this against our teardown timeout
2093
+ await runNextHook(index + 1).catch((e) => hookErrors.push(e));
2094
+ teardownLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
2095
+ // Start teardown timer after inner hooks complete - only times this hook's teardown code
2096
+ teardownTimeout = createTimeoutPromise(timeout, "teardown", stackTraceError);
2097
+ // Signal that use() is returning (teardown phase starting)
2098
+ resolveUseReturned();
2099
+ };
2100
+ setupLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
2101
+ // Start setup timeout
2102
+ setupTimeout = createTimeoutPromise(timeout, "setup", stackTraceError);
2103
+ (async () => {
2104
+ try {
2105
+ await invokeHook(hook, use);
2106
+ if (!useCalled) {
2107
+ 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"}.`);
2108
+ }
2109
+ resolveHookComplete();
2110
+ } catch (error) {
2111
+ rejectHookComplete(error);
2112
+ } finally {
2113
+ setupLimitConcurrencyRelease?.();
2114
+ teardownLimitConcurrencyRelease?.();
2115
+ }
2116
+ })();
2117
+ // Wait for either: use() to be called OR hook to complete (error) OR setup timeout
2118
+ try {
2119
+ await Promise.race([
2120
+ useCalledPromise,
2121
+ hookCompletePromise,
2122
+ setupTimeout.promise
2123
+ ]);
2124
+ } finally {
2125
+ setupLimitConcurrencyRelease?.();
2126
+ setupTimeout.clear();
2127
+ }
2128
+ // Wait for use() to return (inner hooks complete) OR hook to complete (error during inner hooks)
2129
+ await Promise.race([useReturnedPromise, hookCompletePromise]);
2130
+ // Now teardownTimeout is guaranteed to be set
2131
+ // Wait for hook to complete (teardown) OR teardown timeout
2132
+ try {
2133
+ await Promise.race([hookCompletePromise, teardownTimeout?.promise]);
2134
+ } finally {
2135
+ teardownLimitConcurrencyRelease?.();
2136
+ teardownTimeout?.clear();
2137
+ }
2138
+ };
2139
+ await runNextHook(0).catch((e) => hookErrors.push(e));
2140
+ if (hookErrors.length > 0) {
2141
+ throw hookErrors;
2142
+ }
2143
+ }
2144
+ async function callAroundAllHooks(suite, runSuiteInner) {
2145
+ await callAroundHooks(runSuiteInner, {
2146
+ hooks: getAroundAllHooks(suite),
2147
+ hookName: "aroundAll",
2148
+ callbackName: "runSuite()",
2149
+ invokeHook: (hook, use) => hook(use, suite)
2150
+ });
2151
+ }
2152
+ async function callAroundEachHooks(suite, test, runTest) {
2153
+ await callAroundHooks(
2154
+ // Take checkpoint right before runTest - at this point all aroundEach fixtures
2155
+ // have been resolved, so we can correctly identify which fixtures belong to
2156
+ // aroundEach (before checkpoint) vs inside runTest (after checkpoint)
2157
+ () => runTest(getFixtureCleanupCount(test.context)),
2158
+ {
2159
+ hooks: getAroundEachHooks(suite),
2160
+ hookName: "aroundEach",
2161
+ callbackName: "runTest()",
2162
+ onTimeout: (error) => abortContextSignal(test.context, error),
2163
+ invokeHook: (hook, use) => hook(use, test.context, suite)
2164
+ }
2165
+ );
2166
+ }
1531
2167
  const packs = new Map();
1532
2168
  const eventsPacks = [];
1533
2169
  const pendingTasksUpdates = [];
1534
2170
  function sendTasksUpdate(runner) {
1535
2171
  if (packs.size) {
1536
- var _runner$onTaskUpdate;
1537
2172
  const taskPacks = Array.from(packs).map(([id, task]) => {
1538
2173
  return [
1539
2174
  id,
@@ -1541,7 +2176,7 @@ function sendTasksUpdate(runner) {
1541
2176
  task[1]
1542
2177
  ];
1543
2178
  });
1544
- const p = (_runner$onTaskUpdate = runner.onTaskUpdate) === null || _runner$onTaskUpdate === void 0 ? void 0 : _runner$onTaskUpdate.call(runner, taskPacks, eventsPacks);
2179
+ const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
1545
2180
  if (p) {
1546
2181
  pendingTasksUpdates.push(p);
1547
2182
  // remove successful promise to not grow array indefnitely,
@@ -1568,7 +2203,7 @@ function throttle(fn, ms) {
1568
2203
  return fn.apply(this, args);
1569
2204
  }
1570
2205
  // Make sure fn is still called even if there are no further calls
1571
- pendingCall ?? (pendingCall = setTimeout(() => call.bind(this)(...args), ms));
2206
+ pendingCall ??= setTimeout(() => call.bind(this)(...args), ms);
1572
2207
  };
1573
2208
  }
1574
2209
  // throttle based on summary reporter's DURATION_UPDATE_INTERVAL_MS
@@ -1592,26 +2227,44 @@ async function callCleanupHooks(runner, cleanups) {
1592
2227
  if (typeof fn !== "function") {
1593
2228
  return;
1594
2229
  }
1595
- await fn();
2230
+ await limitMaxConcurrency(() => fn());
1596
2231
  }));
1597
2232
  } else {
1598
2233
  for (const fn of cleanups) {
1599
2234
  if (typeof fn !== "function") {
1600
2235
  continue;
1601
2236
  }
1602
- await fn();
2237
+ await limitMaxConcurrency(() => fn());
1603
2238
  }
1604
2239
  }
1605
2240
  }
2241
+ /**
2242
+ * Determines if a test should be retried based on its retryCondition configuration
2243
+ */
2244
+ function passesRetryCondition(test, errors) {
2245
+ const condition = getRetryCondition(test.retry);
2246
+ if (!errors || errors.length === 0) {
2247
+ return false;
2248
+ }
2249
+ if (!condition) {
2250
+ return true;
2251
+ }
2252
+ const error = errors[errors.length - 1];
2253
+ if (condition instanceof RegExp) {
2254
+ return condition.test(error.message || "");
2255
+ } else if (typeof condition === "function") {
2256
+ return condition(error);
2257
+ }
2258
+ return false;
2259
+ }
1606
2260
  async function runTest(test, runner) {
1607
- var _runner$onBeforeRunTa, _test$result, _runner$onAfterRunTas;
1608
- await ((_runner$onBeforeRunTa = runner.onBeforeRunTask) === null || _runner$onBeforeRunTa === void 0 ? void 0 : _runner$onBeforeRunTa.call(runner, test));
2261
+ await runner.onBeforeRunTask?.(test);
1609
2262
  if (test.mode !== "run" && test.mode !== "queued") {
1610
2263
  updateTask("test-prepare", test, runner);
1611
2264
  updateTask("test-finished", test, runner);
1612
2265
  return;
1613
2266
  }
1614
- if (((_test$result = test.result) === null || _test$result === void 0 ? void 0 : _test$result.state) === "fail") {
2267
+ if (test.result?.state === "fail") {
1615
2268
  // should not be possible to get here, I think this is just copy pasted from suite
1616
2269
  // TODO: maybe someone fails tests in `beforeAll` hooks?
1617
2270
  // https://github.com/vitest-dev/vitest/pull/7069
@@ -1631,75 +2284,84 @@ async function runTest(test, runner) {
1631
2284
  const $ = runner.trace;
1632
2285
  const repeats = test.repeats ?? 0;
1633
2286
  for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
1634
- const retry = test.retry ?? 0;
2287
+ const retry = getRetryCount(test.retry);
1635
2288
  for (let retryCount = 0; retryCount <= retry; retryCount++) {
1636
- var _test$onFinished, _test$onFailed, _runner$onAfterRetryT, _test$result2, _test$result3;
1637
2289
  let beforeEachCleanups = [];
1638
- try {
1639
- var _runner$onBeforeTryTa, _runner$onAfterTryTas;
1640
- await ((_runner$onBeforeTryTa = runner.onBeforeTryTask) === null || _runner$onBeforeTryTa === void 0 ? void 0 : _runner$onBeforeTryTa.call(runner, test, {
1641
- retry: retryCount,
1642
- repeats: repeatCount
1643
- }));
1644
- test.result.repeatCount = repeatCount;
1645
- beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
1646
- if (runner.runTask) {
1647
- await $("test.callback", () => runner.runTask(test));
1648
- } else {
1649
- const fn = getFn(test);
1650
- if (!fn) {
1651
- throw new Error("Test function is not found. Did you add it using `setFn`?");
2290
+ // fixtureCheckpoint is passed by callAroundEachHooks - it represents the count
2291
+ // of fixture cleanup functions AFTER all aroundEach fixtures have been resolved
2292
+ // but BEFORE the test runs. This allows us to clean up only fixtures created
2293
+ // inside runTest while preserving aroundEach fixtures for teardown.
2294
+ await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => {
2295
+ try {
2296
+ await runner.onBeforeTryTask?.(test, {
2297
+ retry: retryCount,
2298
+ repeats: repeatCount
2299
+ });
2300
+ test.result.repeatCount = repeatCount;
2301
+ beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
2302
+ if (runner.runTask) {
2303
+ await $("test.callback", () => limitMaxConcurrency(() => runner.runTask(test)));
2304
+ } else {
2305
+ const fn = getFn(test);
2306
+ if (!fn) {
2307
+ throw new Error("Test function is not found. Did you add it using `setFn`?");
2308
+ }
2309
+ await $("test.callback", () => limitMaxConcurrency(() => fn()));
1652
2310
  }
1653
- await $("test.callback", () => fn());
1654
- }
1655
- await ((_runner$onAfterTryTas = runner.onAfterTryTask) === null || _runner$onAfterTryTas === void 0 ? void 0 : _runner$onAfterTryTas.call(runner, test, {
1656
- retry: retryCount,
1657
- repeats: repeatCount
1658
- }));
1659
- if (test.result.state !== "fail") {
1660
- if (!test.repeats) {
1661
- test.result.state = "pass";
1662
- } else if (test.repeats && retry === retryCount) {
2311
+ await runner.onAfterTryTask?.(test, {
2312
+ retry: retryCount,
2313
+ repeats: repeatCount
2314
+ });
2315
+ if (test.result.state !== "fail") {
1663
2316
  test.result.state = "pass";
1664
2317
  }
2318
+ } catch (e) {
2319
+ failTask(test.result, e, runner.config.diffOptions);
1665
2320
  }
1666
- } catch (e) {
1667
- failTask(test.result, e, runner.config.diffOptions);
1668
- }
1669
- try {
1670
- var _runner$onTaskFinishe;
1671
- await ((_runner$onTaskFinishe = runner.onTaskFinished) === null || _runner$onTaskFinishe === void 0 ? void 0 : _runner$onTaskFinishe.call(runner, test));
1672
- } catch (e) {
1673
- failTask(test.result, e, runner.config.diffOptions);
1674
- }
1675
- try {
1676
- await $("test.afterEach", () => callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]));
1677
- if (beforeEachCleanups.length) {
1678
- await $("test.cleanup", () => callCleanupHooks(runner, beforeEachCleanups));
2321
+ try {
2322
+ await runner.onTaskFinished?.(test);
2323
+ } catch (e) {
2324
+ failTask(test.result, e, runner.config.diffOptions);
2325
+ }
2326
+ try {
2327
+ await $("test.afterEach", () => callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]));
2328
+ if (beforeEachCleanups.length) {
2329
+ await $("test.cleanup", () => callCleanupHooks(runner, beforeEachCleanups));
2330
+ }
2331
+ // Only clean up fixtures created inside runTest (after the checkpoint)
2332
+ // Fixtures created for aroundEach will be cleaned up after aroundEach teardown
2333
+ await callFixtureCleanupFrom(test.context, fixtureCheckpoint);
2334
+ } catch (e) {
2335
+ failTask(test.result, e, runner.config.diffOptions);
2336
+ }
2337
+ if (test.onFinished?.length) {
2338
+ await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
2339
+ }
2340
+ if (test.result.state === "fail" && test.onFailed?.length) {
2341
+ await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
1679
2342
  }
2343
+ test.onFailed = undefined;
2344
+ test.onFinished = undefined;
2345
+ await runner.onAfterRetryTask?.(test, {
2346
+ retry: retryCount,
2347
+ repeats: repeatCount
2348
+ });
2349
+ }).catch((error) => {
2350
+ failTask(test.result, error, runner.config.diffOptions);
2351
+ });
2352
+ // Clean up fixtures that were created for aroundEach (before the checkpoint)
2353
+ // This runs after aroundEach teardown has completed
2354
+ try {
1680
2355
  await callFixtureCleanup(test.context);
1681
2356
  } catch (e) {
1682
2357
  failTask(test.result, e, runner.config.diffOptions);
1683
2358
  }
1684
- if ((_test$onFinished = test.onFinished) === null || _test$onFinished === void 0 ? void 0 : _test$onFinished.length) {
1685
- await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
1686
- }
1687
- if (test.result.state === "fail" && ((_test$onFailed = test.onFailed) === null || _test$onFailed === void 0 ? void 0 : _test$onFailed.length)) {
1688
- await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
1689
- }
1690
- test.onFailed = undefined;
1691
- test.onFinished = undefined;
1692
- await ((_runner$onAfterRetryT = runner.onAfterRetryTask) === null || _runner$onAfterRetryT === void 0 ? void 0 : _runner$onAfterRetryT.call(runner, test, {
1693
- retry: retryCount,
1694
- repeats: repeatCount
1695
- }));
1696
2359
  // skipped with new PendingError
1697
- if (((_test$result2 = test.result) === null || _test$result2 === void 0 ? void 0 : _test$result2.pending) || ((_test$result3 = test.result) === null || _test$result3 === void 0 ? void 0 : _test$result3.state) === "skip") {
1698
- var _test$result4;
2360
+ if (test.result?.pending || test.result?.state === "skip") {
1699
2361
  test.mode = "skip";
1700
2362
  test.result = {
1701
2363
  state: "skip",
1702
- note: (_test$result4 = test.result) === null || _test$result4 === void 0 ? void 0 : _test$result4.note,
2364
+ note: test.result?.note,
1703
2365
  pending: true,
1704
2366
  duration: now() - start
1705
2367
  };
@@ -1712,9 +2374,16 @@ async function runTest(test, runner) {
1712
2374
  break;
1713
2375
  }
1714
2376
  if (retryCount < retry) {
1715
- // reset state when retry test
2377
+ const shouldRetry = passesRetryCondition(test, test.result.errors);
2378
+ if (!shouldRetry) {
2379
+ break;
2380
+ }
1716
2381
  test.result.state = "run";
1717
2382
  test.result.retryCount = (test.result.retryCount ?? 0) + 1;
2383
+ const delay = getRetryDelay(test.retry);
2384
+ if (delay > 0) {
2385
+ await new Promise((resolve) => setTimeout(resolve, delay));
2386
+ }
1718
2387
  }
1719
2388
  // update retry info
1720
2389
  updateTask("test-retried", test, runner);
@@ -1734,7 +2403,7 @@ async function runTest(test, runner) {
1734
2403
  cleanupRunningTest();
1735
2404
  setCurrentTest(undefined);
1736
2405
  test.result.duration = now() - start;
1737
- await ((_runner$onAfterRunTas = runner.onAfterRunTask) === null || _runner$onAfterRunTas === void 0 ? void 0 : _runner$onAfterRunTas.call(runner, test));
2406
+ await runner.onAfterRunTask?.(test);
1738
2407
  updateTask("test-finished", test, runner);
1739
2408
  }
1740
2409
  function failTask(result, err, diffOptions) {
@@ -1744,12 +2413,17 @@ function failTask(result, err, diffOptions) {
1744
2413
  result.pending = true;
1745
2414
  return;
1746
2415
  }
2416
+ if (err instanceof TestRunAbortError) {
2417
+ result.state = "skip";
2418
+ result.note = err.message;
2419
+ return;
2420
+ }
1747
2421
  result.state = "fail";
1748
2422
  const errors = Array.isArray(err) ? err : [err];
1749
2423
  for (const e of errors) {
1750
- const error = processError(e, diffOptions);
1751
- result.errors ?? (result.errors = []);
1752
- result.errors.push(error);
2424
+ const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, diffOptions)) : [processError(e, diffOptions)];
2425
+ result.errors ??= [];
2426
+ result.errors.push(...errors);
1753
2427
  }
1754
2428
  }
1755
2429
  function markTasksAsSkipped(suite, runner) {
@@ -1765,10 +2439,25 @@ function markTasksAsSkipped(suite, runner) {
1765
2439
  }
1766
2440
  });
1767
2441
  }
2442
+ function markPendingTasksAsSkipped(suite, runner, note) {
2443
+ suite.tasks.forEach((t) => {
2444
+ if (!t.result || t.result.state === "run") {
2445
+ t.mode = "skip";
2446
+ t.result = {
2447
+ ...t.result,
2448
+ state: "skip",
2449
+ note
2450
+ };
2451
+ updateTask("test-cancel", t, runner);
2452
+ }
2453
+ if (t.type === "suite") {
2454
+ markPendingTasksAsSkipped(t, runner, note);
2455
+ }
2456
+ });
2457
+ }
1768
2458
  async function runSuite(suite, runner) {
1769
- var _runner$onBeforeRunSu, _suite$result;
1770
- await ((_runner$onBeforeRunSu = runner.onBeforeRunSuite) === null || _runner$onBeforeRunSu === void 0 ? void 0 : _runner$onBeforeRunSu.call(runner, suite));
1771
- if (((_suite$result = suite.result) === null || _suite$result === void 0 ? void 0 : _suite$result.state) === "fail") {
2459
+ await runner.onBeforeRunSuite?.(suite);
2460
+ if (suite.result?.state === "fail") {
1772
2461
  markTasksAsSkipped(suite, runner);
1773
2462
  // failed during collection
1774
2463
  updateTask("suite-failed-early", suite, runner);
@@ -1790,55 +2479,68 @@ async function runSuite(suite, runner) {
1790
2479
  suite.result.state = "todo";
1791
2480
  updateTask("suite-finished", suite, runner);
1792
2481
  } else {
1793
- var _runner$onAfterRunSui;
2482
+ let suiteRan = false;
1794
2483
  try {
1795
- try {
1796
- beforeAllCleanups = await $("suite.beforeAll", () => callSuiteHook(suite, suite, "beforeAll", runner, [suite]));
1797
- } catch (e) {
1798
- markTasksAsSkipped(suite, runner);
1799
- throw e;
1800
- }
1801
- if (runner.runSuite) {
1802
- await runner.runSuite(suite);
1803
- } else {
1804
- for (let tasksGroup of partitionSuiteChildren(suite)) {
1805
- if (tasksGroup[0].concurrent === true) {
1806
- await Promise.all(tasksGroup.map((c) => runSuiteChild(c, runner)));
2484
+ await callAroundAllHooks(suite, async () => {
2485
+ suiteRan = true;
2486
+ try {
2487
+ // beforeAll
2488
+ try {
2489
+ beforeAllCleanups = await $("suite.beforeAll", () => callSuiteHook(suite, suite, "beforeAll", runner, [suite]));
2490
+ } catch (e) {
2491
+ failTask(suite.result, e, runner.config.diffOptions);
2492
+ markTasksAsSkipped(suite, runner);
2493
+ return;
2494
+ }
2495
+ // run suite children
2496
+ if (runner.runSuite) {
2497
+ await runner.runSuite(suite);
1807
2498
  } else {
1808
- const { sequence } = runner.config;
1809
- if (suite.shuffle) {
1810
- // run describe block independently from tests
1811
- const suites = tasksGroup.filter((group) => group.type === "suite");
1812
- const tests = tasksGroup.filter((group) => group.type === "test");
1813
- const groups = shuffle([suites, tests], sequence.seed);
1814
- tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
2499
+ for (let tasksGroup of partitionSuiteChildren(suite)) {
2500
+ if (tasksGroup[0].concurrent === true) {
2501
+ await Promise.all(tasksGroup.map((c) => runSuiteChild(c, runner)));
2502
+ } else {
2503
+ const { sequence } = runner.config;
2504
+ if (suite.shuffle) {
2505
+ // run describe block independently from tests
2506
+ const suites = tasksGroup.filter((group) => group.type === "suite");
2507
+ const tests = tasksGroup.filter((group) => group.type === "test");
2508
+ const groups = shuffle([suites, tests], sequence.seed);
2509
+ tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
2510
+ }
2511
+ for (const c of tasksGroup) {
2512
+ await runSuiteChild(c, runner);
2513
+ }
2514
+ }
2515
+ }
2516
+ }
2517
+ } finally {
2518
+ // afterAll runs even if beforeAll or suite children fail
2519
+ try {
2520
+ await $("suite.afterAll", () => callSuiteHook(suite, suite, "afterAll", runner, [suite]));
2521
+ if (beforeAllCleanups.length) {
2522
+ await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
1815
2523
  }
1816
- for (const c of tasksGroup) {
1817
- await runSuiteChild(c, runner);
2524
+ if (suite.file === suite) {
2525
+ const contexts = TestFixtures.getFileContexts(suite.file);
2526
+ await Promise.all(contexts.map((context) => callFixtureCleanup(context)));
1818
2527
  }
2528
+ } catch (e) {
2529
+ failTask(suite.result, e, runner.config.diffOptions);
1819
2530
  }
1820
2531
  }
1821
- }
2532
+ });
1822
2533
  } catch (e) {
1823
- failTask(suite.result, e, runner.config.diffOptions);
1824
- }
1825
- try {
1826
- await $("suite.afterAll", () => callSuiteHook(suite, suite, "afterAll", runner, [suite]));
1827
- if (beforeAllCleanups.length) {
1828
- await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
1829
- }
1830
- if (suite.file === suite) {
1831
- const context = getFileContext(suite);
1832
- await callFixtureCleanup(context);
2534
+ // mark tasks as skipped if aroundAll failed before the suite callback was executed
2535
+ if (!suiteRan) {
2536
+ markTasksAsSkipped(suite, runner);
1833
2537
  }
1834
- } catch (e) {
1835
2538
  failTask(suite.result, e, runner.config.diffOptions);
1836
2539
  }
1837
2540
  if (suite.mode === "run" || suite.mode === "queued") {
1838
2541
  if (!runner.config.passWithNoTests && !hasTests(suite)) {
1839
- var _suite$result$errors;
1840
2542
  suite.result.state = "fail";
1841
- if (!((_suite$result$errors = suite.result.errors) === null || _suite$result$errors === void 0 ? void 0 : _suite$result$errors.length)) {
2543
+ if (!suite.result.errors?.length) {
1842
2544
  const error = processError(new Error(`No test found in suite ${suite.name}`));
1843
2545
  suite.result.errors = [error];
1844
2546
  }
@@ -1849,44 +2551,38 @@ async function runSuite(suite, runner) {
1849
2551
  }
1850
2552
  }
1851
2553
  suite.result.duration = now() - start;
1852
- await ((_runner$onAfterRunSui = runner.onAfterRunSuite) === null || _runner$onAfterRunSui === void 0 ? void 0 : _runner$onAfterRunSui.call(runner, suite));
2554
+ await runner.onAfterRunSuite?.(suite);
1853
2555
  updateTask("suite-finished", suite, runner);
1854
2556
  }
1855
2557
  }
1856
- let limitMaxConcurrency;
1857
2558
  async function runSuiteChild(c, runner) {
1858
2559
  const $ = runner.trace;
1859
2560
  if (c.type === "test") {
1860
- return limitMaxConcurrency(() => {
1861
- var _c$location, _c$location2;
1862
- return $("run.test", {
1863
- "vitest.test.id": c.id,
1864
- "vitest.test.name": c.name,
1865
- "vitest.test.mode": c.mode,
1866
- "vitest.test.timeout": c.timeout,
1867
- "code.file.path": c.file.filepath,
1868
- "code.line.number": (_c$location = c.location) === null || _c$location === void 0 ? void 0 : _c$location.line,
1869
- "code.column.number": (_c$location2 = c.location) === null || _c$location2 === void 0 ? void 0 : _c$location2.column
1870
- }, () => runTest(c, runner));
1871
- });
2561
+ 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));
1872
2570
  } else if (c.type === "suite") {
1873
- var _c$location3, _c$location4;
1874
2571
  return $("run.suite", {
1875
2572
  "vitest.suite.id": c.id,
1876
2573
  "vitest.suite.name": c.name,
1877
2574
  "vitest.suite.mode": c.mode,
1878
2575
  "code.file.path": c.file.filepath,
1879
- "code.line.number": (_c$location3 = c.location) === null || _c$location3 === void 0 ? void 0 : _c$location3.line,
1880
- "code.column.number": (_c$location4 = c.location) === null || _c$location4 === void 0 ? void 0 : _c$location4.column
2576
+ "code.line.number": c.location?.line,
2577
+ "code.column.number": c.location?.column
1881
2578
  }, () => runSuite(c, runner));
1882
2579
  }
1883
2580
  }
1884
2581
  async function runFiles(files, runner) {
1885
- limitMaxConcurrency ?? (limitMaxConcurrency = limitConcurrency(runner.config.maxConcurrency));
2582
+ limitMaxConcurrency ??= limitConcurrency(runner.config.maxConcurrency);
1886
2583
  for (const file of files) {
1887
2584
  if (!file.tasks.length && !runner.config.passWithNoTests) {
1888
- var _file$result;
1889
- if (!((_file$result = file.result) === null || _file$result === void 0 || (_file$result = _file$result.errors) === null || _file$result === void 0 ? void 0 : _file$result.length)) {
2585
+ if (!file.result?.errors?.length) {
1890
2586
  const error = processError(new Error(`No test suite found in file ${file.filepath}`));
1891
2587
  file.result = {
1892
2588
  state: "fail",
@@ -1908,37 +2604,35 @@ function defaultTrace(_, attributes, cb) {
1908
2604
  return cb();
1909
2605
  }
1910
2606
  async function startTests(specs, runner) {
1911
- var _runner$cancel;
1912
- runner.trace ?? (runner.trace = defaultTrace);
1913
- const cancel = (_runner$cancel = runner.cancel) === null || _runner$cancel === void 0 ? void 0 : _runner$cancel.bind(runner);
2607
+ runner.trace ??= defaultTrace;
2608
+ const cancel = runner.cancel?.bind(runner);
1914
2609
  // Ideally, we need to have an event listener for this, but only have a runner here.
1915
2610
  // Adding another onCancel felt wrong (maybe it needs to be refactored)
1916
2611
  runner.cancel = (reason) => {
1917
2612
  // We intentionally create only one error since there is only one test run that can be cancelled
1918
2613
  const error = new TestRunAbortError("The test run was aborted by the user.", reason);
1919
- getRunningTests().forEach((test) => abortContextSignal(test.context, error));
1920
- return cancel === null || cancel === void 0 ? void 0 : cancel(reason);
2614
+ getRunningTests().forEach((test) => {
2615
+ abortContextSignal(test.context, error);
2616
+ markPendingTasksAsSkipped(test.file, runner, error.message);
2617
+ });
2618
+ return cancel?.(reason);
1921
2619
  };
1922
2620
  if (!workerRunners.has(runner)) {
1923
- var _runner$onCleanupWork;
1924
- (_runner$onCleanupWork = runner.onCleanupWorkerContext) === null || _runner$onCleanupWork === void 0 ? void 0 : _runner$onCleanupWork.call(runner, async () => {
1925
- var _runner$getWorkerCont;
1926
- const context = (_runner$getWorkerCont = runner.getWorkerContext) === null || _runner$getWorkerCont === void 0 ? void 0 : _runner$getWorkerCont.call(runner);
1927
- if (context) {
1928
- await callFixtureCleanup(context);
1929
- }
2621
+ runner.onCleanupWorkerContext?.(async () => {
2622
+ await Promise.all([...TestFixtures.getWorkerContexts()].map((context) => callFixtureCleanup(context))).finally(() => {
2623
+ TestFixtures.clearDefinitions();
2624
+ });
1930
2625
  });
1931
2626
  workerRunners.add(runner);
1932
2627
  }
1933
2628
  try {
1934
- var _runner$onBeforeColle, _runner$onCollected, _runner$onBeforeRunFi, _runner$onAfterRunFil;
1935
2629
  const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
1936
- await ((_runner$onBeforeColle = runner.onBeforeCollect) === null || _runner$onBeforeColle === void 0 ? void 0 : _runner$onBeforeColle.call(runner, paths));
2630
+ await runner.onBeforeCollect?.(paths);
1937
2631
  const files = await collectTests(specs, runner);
1938
- await ((_runner$onCollected = runner.onCollected) === null || _runner$onCollected === void 0 ? void 0 : _runner$onCollected.call(runner, files));
1939
- await ((_runner$onBeforeRunFi = runner.onBeforeRunFiles) === null || _runner$onBeforeRunFi === void 0 ? void 0 : _runner$onBeforeRunFi.call(runner, files));
2632
+ await runner.onCollected?.(files);
2633
+ await runner.onBeforeRunFiles?.(files);
1940
2634
  await runFiles(files, runner);
1941
- await ((_runner$onAfterRunFil = runner.onAfterRunFiles) === null || _runner$onAfterRunFil === void 0 ? void 0 : _runner$onAfterRunFil.call(runner, files));
2635
+ await runner.onAfterRunFiles?.(files);
1942
2636
  await finishSendTasksUpdate(runner);
1943
2637
  return files;
1944
2638
  } finally {
@@ -1946,12 +2640,11 @@ async function startTests(specs, runner) {
1946
2640
  }
1947
2641
  }
1948
2642
  async function publicCollect(specs, runner) {
1949
- var _runner$onBeforeColle2, _runner$onCollected2;
1950
- runner.trace ?? (runner.trace = defaultTrace);
2643
+ runner.trace ??= defaultTrace;
1951
2644
  const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
1952
- await ((_runner$onBeforeColle2 = runner.onBeforeCollect) === null || _runner$onBeforeColle2 === void 0 ? void 0 : _runner$onBeforeColle2.call(runner, paths));
2645
+ await runner.onBeforeCollect?.(paths);
1953
2646
  const files = await collectTests(specs, runner);
1954
- await ((_runner$onCollected2 = runner.onCollected) === null || _runner$onCollected2 === void 0 ? void 0 : _runner$onCollected2.call(runner, files));
2647
+ await runner.onCollected?.(files);
1955
2648
  return files;
1956
2649
  }
1957
2650
 
@@ -1965,12 +2658,13 @@ async function publicCollect(specs, runner) {
1965
2658
  *
1966
2659
  * Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
1967
2660
  *
2661
+ * **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
2662
+ *
1968
2663
  * @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
1969
2664
  * @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
1970
2665
  *
1971
2666
  * @returns A promise that resolves to the recorded artifact with location injected
1972
2667
  *
1973
- * @throws {Error} If called after the test has finished running
1974
2668
  * @throws {Error} If the test runner doesn't support artifacts
1975
2669
  *
1976
2670
  * @example
@@ -1991,9 +2685,6 @@ async function publicCollect(specs, runner) {
1991
2685
  */
1992
2686
  async function recordArtifact(task, artifact) {
1993
2687
  const runner = getRunner();
1994
- if (task.result && task.result.state !== "run") {
1995
- throw new Error(`Cannot record a test artifact outside of the test run. The test "${task.name}" finished running with the "${task.result.state}" state already.`);
1996
- }
1997
2688
  const stack = findTestFileStackTrace(task.file.filepath, new Error("STACK_TRACE").stack);
1998
2689
  if (stack) {
1999
2690
  artifact.location = {
@@ -2111,4 +2802,4 @@ function manageArtifactAttachment(attachment) {
2111
2802
  }
2112
2803
  }
2113
2804
 
2114
- export { afterAll, afterEach, beforeAll, beforeEach, publicCollect as collectTests, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, recordArtifact, setFn, setHooks, startTests, suite, test, updateTask };
2805
+ export { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach, publicCollect as collectTests, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, recordArtifact, setFn, setHooks, startTests, suite, test, updateTask };