@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.
- package/LICENSE.md +181 -1
- package/dist/@vitest/browser/client/.vite/manifest.json +6 -6
- package/dist/@vitest/browser/client/__vitest__/assets/index-Da0hb3oU.css +1 -0
- package/dist/@vitest/browser/client/__vitest__/assets/index-Di71CKDo.js +63 -0
- package/dist/@vitest/browser/client/__vitest__/favicon.ico +0 -0
- package/dist/@vitest/browser/client/__vitest__/favicon.svg +49 -4
- package/dist/@vitest/browser/client/__vitest__/index.html +2 -2
- package/dist/@vitest/browser/client/__vitest_browser__/{orchestrator-S_3e_uzt.js → orchestrator-CXs6qrFe.js} +70 -28
- package/dist/@vitest/browser/client/__vitest_browser__/{tester-k74mgIRa.js → tester-K5NNxh1O.js} +167 -58
- package/dist/@vitest/browser/client/__vitest_browser__/{utils-uxqdqUz8.js → utils-C2ISqq1C.js} +2 -2
- package/dist/@vitest/browser/client/favicon.svg +49 -4
- package/dist/@vitest/browser/client/orchestrator.html +2 -2
- package/dist/@vitest/browser/client/tester/tester.html +2 -2
- package/dist/@vitest/browser/client.js +20 -13
- package/dist/@vitest/browser/context.d.ts +160 -10
- package/dist/@vitest/browser/context.js +108 -22
- package/dist/@vitest/browser/expect-element.js +23 -28
- package/dist/@vitest/browser/index-5Pe7X7sp.js +7 -0
- package/dist/@vitest/browser/index.d.ts +20 -2
- package/dist/@vitest/browser/index.js +5706 -159
- package/dist/@vitest/browser/locators.d.ts +14 -3
- package/dist/@vitest/browser/locators.js +1 -1
- package/dist/@vitest/browser-playwright/index.d.ts +22 -5
- package/dist/@vitest/browser-playwright/index.js +169 -61
- package/dist/@vitest/browser-preview/index.d.ts +14 -1
- package/dist/@vitest/browser-preview/locators.js +31 -18
- package/dist/@vitest/browser-webdriverio/index.d.ts +17 -3
- package/dist/@vitest/browser-webdriverio/index.js +22 -2
- package/dist/@vitest/browser-webdriverio/locators.js +84 -7
- package/dist/@vitest/expect/index.d.ts +172 -54
- package/dist/@vitest/expect/index.js +124 -67
- package/dist/@vitest/mocker/auto-register.js +1 -0
- package/dist/@vitest/mocker/automock.d.ts +1 -0
- package/dist/@vitest/mocker/automock.js +5 -0
- package/dist/@vitest/mocker/browser.d.ts +4 -4
- package/dist/@vitest/mocker/browser.js +1 -0
- package/dist/@vitest/mocker/chunk-automock.js +182 -14
- package/dist/@vitest/mocker/chunk-helpers.js +44 -0
- package/dist/@vitest/mocker/chunk-hoistMocks.js +659 -0
- package/dist/@vitest/mocker/chunk-mocker.js +41 -30
- package/dist/@vitest/mocker/chunk-registry.js +21 -7
- package/dist/@vitest/mocker/chunk-utils.js +18 -7
- package/dist/@vitest/mocker/hoistMocks.d-w2ILr1dG.d.ts +739 -0
- package/dist/@vitest/mocker/{index.d-C-sLYZi-.d.ts → index.d-B41z0AuW.d.ts} +1 -1
- package/dist/@vitest/mocker/index.d.ts +2 -2
- package/dist/@vitest/mocker/index.js +18 -3
- package/dist/@vitest/mocker/{mocker.d-TnKRhz7N.d.ts → mocker.d-QEntlm6J.d.ts} +10 -5
- package/dist/@vitest/mocker/node.d.ts +5 -734
- package/dist/@vitest/mocker/node.js +29 -587
- package/dist/@vitest/mocker/redirect.js +4 -4
- package/dist/@vitest/mocker/register.d.ts +3 -3
- package/dist/@vitest/mocker/register.js +1 -0
- package/dist/@vitest/mocker/transforms.d.ts +6 -0
- package/dist/@vitest/mocker/transforms.js +8 -0
- package/dist/@vitest/mocker/{types.d-B8CCKmHt.d.ts → types.d-BjI5eAwu.d.ts} +23 -7
- package/dist/@vitest/pretty-format/index.d.ts +11 -1
- package/dist/@vitest/pretty-format/index.js +33 -4
- package/dist/@vitest/runner/chunk-tasks.js +305 -37
- package/dist/@vitest/runner/index.d.ts +5 -6
- package/dist/@vitest/runner/index.js +1146 -455
- package/dist/@vitest/runner/{tasks.d-C7UxawJ9.d.ts → tasks.d-D2GKpdwQ.d.ts} +726 -55
- package/dist/@vitest/runner/types.d.ts +2 -182
- package/dist/@vitest/runner/utils.d.ts +16 -8
- package/dist/@vitest/runner/utils.js +1 -1
- package/dist/@vitest/snapshot/{environment.d-DHdQ1Csl.d.ts → environment.d-DOJxxZV9.d.ts} +2 -7
- package/dist/@vitest/snapshot/environment.d.ts +2 -1
- package/dist/@vitest/snapshot/environment.js +1 -1
- package/dist/@vitest/snapshot/index.d.ts +4 -3
- package/dist/@vitest/snapshot/index.js +21 -550
- package/dist/@vitest/snapshot/manager.d.ts +3 -2
- package/dist/@vitest/snapshot/manager.js +1 -1
- package/dist/@vitest/snapshot/{rawSnapshot.d-lFsMJFUd.d.ts → rawSnapshot.d-U2kJUxDr.d.ts} +1 -1
- package/dist/@vitest/spy/index.d.ts +34 -4
- package/dist/@vitest/spy/index.js +69 -19
- package/dist/@vitest/utils/diff.js +11 -9
- package/dist/@vitest/utils/display.d.ts +2 -1
- package/dist/@vitest/utils/display.js +38 -5
- package/dist/@vitest/utils/error.d.ts +2 -1
- package/dist/@vitest/utils/error.js +1 -2
- package/dist/@vitest/utils/helpers.d.ts +4 -1
- package/dist/@vitest/utils/helpers.js +43 -1
- package/dist/@vitest/utils/resolver.js +1 -2
- package/dist/@vitest/utils/serialize.js +6 -6
- package/dist/@vitest/utils/source-map/node.d.ts +6 -0
- package/dist/@vitest/utils/source-map/node.js +23 -0
- package/dist/@vitest/utils/source-map.js +15 -5
- package/dist/browser.d.ts +3 -2
- package/dist/browser.js +2 -2
- package/dist/chunks/acorn.B2iPLyUM.js +5958 -0
- package/dist/chunks/{base.CJ0Y4ePK.js → base.DM0-RqVb.js} +54 -16
- package/dist/chunks/{benchmark.B3N2zMcH.js → benchmark.D0SlKNbZ.js} +1 -1
- package/dist/chunks/{browser.d.ChKACdzH.d.ts → browser.d.X3SXoOCV.d.ts} +4 -1
- package/dist/chunks/{cac.DVeoLl0M.js → cac.CWGDZnXT.js} +979 -20
- package/dist/chunks/{cli-api.B7PN_QUv.js → cli-api.DuT9iuvY.js} +8764 -7898
- package/dist/chunks/{config.d.Cy95HiCx.d.ts → config.d.EJLVE3es.d.ts} +30 -15
- package/dist/chunks/{console.Cf-YriPC.js → console.3WNpx0tS.js} +3 -2
- package/dist/chunks/{constants.D_Q9UYh-.js → constants.CPYnjOGj.js} +4 -2
- package/dist/chunks/coverage.Bri33R1t.js +1050 -0
- package/dist/chunks/{creator.DAmOKTvJ.js → creator.DgVhQm5q.js} +35 -4
- package/dist/chunks/{defaults.BOqNVLsY.js → defaults.CdU2lD-q.js} +4 -3
- package/dist/chunks/{global.d.B15mdLcR.d.ts → global.d.x-ILCfAE.d.ts} +1 -2
- package/dist/chunks/{globals.DOayXfHP.js → globals.BXNGLnTL.js} +11 -10
- package/dist/chunks/{coverage.AVPTjMgw.js → index.BCY_7LL2.js} +5 -959
- package/dist/chunks/{index.C5r1PdPD.js → index.CEzQDJGb.js} +1 -1
- package/dist/chunks/{index.D3XRDfWc.js → index.CMESou6r.js} +26 -1
- package/dist/chunks/{index.6Qv1eEA6.js → index.DGNSnENe.js} +95 -9
- package/dist/chunks/{index.M8mOzt4Y.js → index.DXMFO5MJ.js} +3279 -2914
- package/dist/chunks/{index.Z5E_ObnR.js → index.DlDSLQD3.js} +7 -3
- package/dist/chunks/{index.CyBMJtT7.js → index.EY6TCHpo.js} +10 -8
- package/dist/chunks/{index.D4KonVSU.js → index.og1WyBLx.js} +18 -3
- package/dist/chunks/{init-forks._y3TW739.js → init-forks.DeArv0jT.js} +1 -1
- package/dist/chunks/{init-threads.DBO2kn-p.js → init-threads.-2OUl4Nn.js} +1 -1
- package/dist/chunks/{init.B6MLFIaN.js → init.DICorXCo.js} +52 -13
- package/dist/chunks/native.DPzPHdi5.js +148 -0
- package/dist/chunks/nativeModuleMocker.DndvSdL6.js +206 -0
- package/dist/chunks/nativeModuleRunner.BIakptoF.js +36 -0
- package/dist/chunks/{node.Ce0vMQM7.js → node.COQbm6gK.js} +1 -1
- package/dist/chunks/{plugin.d.CtqpEehP.d.ts → plugin.d.BuW-flqo.d.ts} +1 -1
- package/dist/chunks/{reporters.d.CWXNI2jG.d.ts → reporters.d.DVUYHHhe.d.ts} +328 -79
- package/dist/chunks/rpc.MzXet3jl.js +144 -0
- package/dist/chunks/{rpc.d.RH3apGEf.d.ts → rpc.d.BFMWpdph.d.ts} +10 -2
- package/dist/chunks/{setup-common.Cm-kSBVi.js → setup-common.B41N_kPE.js} +3 -3
- package/dist/chunks/{startModuleRunner.DEj0jb3e.js → startVitestModuleRunner.C3ZR-4J3.js} +265 -405
- package/dist/chunks/{suite.d.BJWk38HB.d.ts → suite.d.udJtyAgw.d.ts} +1 -1
- package/dist/chunks/{vi.2VT5v0um.js → test.CTcmp4Su.js} +538 -181
- package/dist/chunks/{utils.DvEY5TfP.js → utils.BX5Fg8C4.js} +8 -1
- package/dist/chunks/{vm.D3epNOPZ.js → vm.Dh2rTtmP.js} +48 -8
- package/dist/chunks/{worker.d.Dyxm8DEL.d.ts → worker.d.B84sVRy0.d.ts} +2 -2
- package/dist/cli.js +6 -5
- package/dist/client/.vite/manifest.json +6 -6
- package/dist/client/__vitest__/assets/index-Da0hb3oU.css +1 -0
- package/dist/client/__vitest__/assets/index-Di71CKDo.js +63 -0
- package/dist/client/__vitest__/favicon.ico +0 -0
- package/dist/client/__vitest__/favicon.svg +49 -4
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/{orchestrator-S_3e_uzt.js → orchestrator-CXs6qrFe.js} +70 -28
- package/dist/client/__vitest_browser__/{tester-k74mgIRa.js → tester-K5NNxh1O.js} +167 -58
- package/dist/client/__vitest_browser__/{utils-uxqdqUz8.js → utils-C2ISqq1C.js} +2 -2
- package/dist/client/favicon.svg +49 -4
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client.js +20 -13
- package/dist/config.cjs +3 -2
- package/dist/config.d.ts +13 -12
- package/dist/config.js +2 -2
- package/dist/context.js +108 -22
- package/dist/coverage.d.ts +12 -8
- package/dist/coverage.js +8 -5
- package/dist/environments.js +3 -1
- package/dist/expect-element.js +23 -23
- package/dist/index-5Pe7X7sp.js +7 -0
- package/dist/index.d.ts +66 -27
- package/dist/index.js +10 -9
- package/dist/locators.d.ts +14 -3
- package/dist/locators.js +1 -1
- package/dist/module-evaluator.d.ts +11 -1
- package/dist/module-evaluator.js +43 -26
- package/dist/node.d.ts +28 -14
- package/dist/node.js +42 -40
- package/dist/nodejs-worker-loader.js +41 -0
- package/dist/plugins/mocker-transforms.mjs +2 -0
- package/dist/plugins/utils-source-map-node.mjs +2 -0
- package/dist/reporters.d.ts +8 -8
- package/dist/reporters.js +7 -5
- package/dist/runners.d.ts +24 -5
- package/dist/runners.js +6 -6
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.js +35 -0
- package/dist/snapshot.js +4 -2
- package/dist/suite.d.ts +1 -1
- package/dist/suite.js +4 -2
- package/dist/vendor/blazediff_core.d.mts +1 -0
- package/dist/vendor/blazediff_core.mjs +117 -0
- package/dist/vendor/chai.mjs +4 -249
- package/dist/vendor/convert-source-map.d.mts +1 -0
- package/dist/vendor/convert-source-map.mjs +150 -0
- package/dist/vendor/expect-type.d.mts +14 -7
- package/dist/vendor/expect-type.mjs +5 -5
- package/dist/vendor/std-env.d.mts +131 -40
- package/dist/vendor/std-env.mjs +114 -117
- package/dist/worker.d.ts +6 -6
- package/dist/worker.js +27 -21
- package/dist/workers/forks.js +23 -17
- package/dist/workers/runVmTests.js +18 -16
- package/dist/workers/threads.js +23 -17
- package/dist/workers/vmForks.js +15 -12
- package/dist/workers/vmThreads.js +15 -12
- package/globals.d.ts +2 -0
- package/package.json +36 -27
- package/suppress-warnings.cjs +1 -0
- package/dist/@vitest/browser/client/__vitest__/assets/index-BUCFJtth.js +0 -57
- package/dist/@vitest/browser/client/__vitest__/assets/index-DlhE0rqZ.css +0 -1
- package/dist/@vitest/browser/index-D6m36C6U.js +0 -11
- package/dist/@vitest/utils/chunk-_commonjsHelpers.js +0 -5
- package/dist/@vitest/utils/highlight.d.ts +0 -9
- package/dist/@vitest/utils/highlight.js +0 -538
- package/dist/chunks/date.Bq6ZW5rf.js +0 -73
- package/dist/chunks/rpc.BoxB0q7B.js +0 -76
- package/dist/chunks/test.B8ej_ZHS.js +0 -254
- package/dist/client/__vitest__/assets/index-BUCFJtth.js +0 -57
- package/dist/client/__vitest__/assets/index-DlhE0rqZ.css +0 -1
- package/dist/index-D6m36C6U.js +0 -6
- package/dist/mocker.d.ts +0 -1
- package/dist/mocker.js +0 -1
- 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 {
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 =
|
|
144
|
-
if (!
|
|
290
|
+
const fixtures = options?.fixtures || getTestFixtures(context);
|
|
291
|
+
if (!fixtures) {
|
|
145
292
|
return fn(context);
|
|
146
293
|
}
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
if (!usedProps.length && !hasAutoFixture) {
|
|
294
|
+
const registrations = fixtures.get(suite);
|
|
295
|
+
if (!registrations.size) {
|
|
150
296
|
return fn(context);
|
|
151
297
|
}
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
161
|
-
const pendingFixtures = resolveDeps(usedFixtures);
|
|
312
|
+
const pendingFixtures = resolveDeps(usedFixtures, registrations);
|
|
162
313
|
if (!pendingFixtures.length) {
|
|
163
314
|
return fn(context);
|
|
164
315
|
}
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
358
|
+
return fn(context);
|
|
182
359
|
};
|
|
183
360
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
|
215
|
-
return
|
|
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,
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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(
|
|
258
|
-
|
|
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.
|
|
433
|
+
if (!isFixtureFunction(fixture.value) || !fixture.deps) {
|
|
263
434
|
pendingFixtures.push(fixture);
|
|
264
435
|
return;
|
|
265
436
|
}
|
|
266
437
|
if (depSet.has(fixture)) {
|
|
267
|
-
|
|
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
|
|
277
|
-
|
|
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
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
if (!first) {
|
|
298
|
-
return [];
|
|
299
|
-
}
|
|
498
|
+
const fixturesArgument = args[fixturesIndex];
|
|
499
|
+
if (!fixturesArgument) {
|
|
500
|
+
return memoProps(fn, new Set());
|
|
300
501
|
}
|
|
301
|
-
if (!(
|
|
302
|
-
|
|
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 =
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
464
|
-
|
|
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
|
|
489
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
757
|
-
const
|
|
758
|
-
const
|
|
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([
|
|
763
|
-
fullTestName: createTaskName([currentSuite
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
822
|
-
options.
|
|
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 =
|
|
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([
|
|
867
|
-
fullTestName: createTaskName([currentSuite
|
|
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:
|
|
872
|
-
shuffle: suiteOptions
|
|
1213
|
+
file: currentSuite?.file ?? collectorContext.currentSuite?.file,
|
|
1214
|
+
shuffle: suiteOptions?.shuffle,
|
|
873
1215
|
tasks: [],
|
|
874
|
-
meta: Object.create(null),
|
|
875
|
-
concurrent: suiteOptions
|
|
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
|
-
...
|
|
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
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
|
956
|
-
|
|
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
|
-
|
|
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
|
|
1362
|
+
function createTaskCollector(fn) {
|
|
1008
1363
|
const taskFn = fn;
|
|
1009
1364
|
taskFn.each = function(cases, ...args) {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
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
|
-
|
|
1392
|
+
context.setContext("each", undefined);
|
|
1037
1393
|
};
|
|
1038
1394
|
};
|
|
1039
1395
|
taskFn.for = function(cases, ...args) {
|
|
1040
|
-
const
|
|
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
|
|
1052
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
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(
|
|
1069
|
-
const
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
|
1099
|
-
return createTaskCollector(fn
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1633
|
+
timer.unref?.();
|
|
1217
1634
|
function rejectTimeoutError() {
|
|
1218
1635
|
const error = makeTimeoutError(isHook, timeout, stackTraceError);
|
|
1219
|
-
onTimeout
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1839
|
+
errors
|
|
1415
1840
|
};
|
|
1416
|
-
const durations =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
if (
|
|
1651
|
-
|
|
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
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
await
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
1751
|
-
result.errors
|
|
1752
|
-
result.errors.push(
|
|
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
|
-
|
|
1770
|
-
|
|
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
|
-
|
|
2482
|
+
let suiteRan = false;
|
|
1794
2483
|
try {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
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":
|
|
1880
|
-
"code.column.number":
|
|
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
|
|
2582
|
+
limitMaxConcurrency ??= limitConcurrency(runner.config.maxConcurrency);
|
|
1886
2583
|
for (const file of files) {
|
|
1887
2584
|
if (!file.tasks.length && !runner.config.passWithNoTests) {
|
|
1888
|
-
|
|
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
|
-
|
|
1912
|
-
runner.
|
|
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) =>
|
|
1920
|
-
|
|
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
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
|
2630
|
+
await runner.onBeforeCollect?.(paths);
|
|
1937
2631
|
const files = await collectTests(specs, runner);
|
|
1938
|
-
await
|
|
1939
|
-
await
|
|
2632
|
+
await runner.onCollected?.(files);
|
|
2633
|
+
await runner.onBeforeRunFiles?.(files);
|
|
1940
2634
|
await runFiles(files, runner);
|
|
1941
|
-
await
|
|
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
|
-
|
|
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
|
|
2645
|
+
await runner.onBeforeCollect?.(paths);
|
|
1953
2646
|
const files = await collectTests(specs, runner);
|
|
1954
|
-
await
|
|
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 };
|