@powerlines/plugin-open-telemetry 0.1.111 → 0.1.113
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/dist/_virtual/_rolldown/runtime.cjs +14 -0
- package/dist/_virtual/_rolldown/runtime.mjs +18 -0
- package/dist/components/trace-builtin.cjs +10 -3
- package/dist/components/trace-builtin.mjs +4 -2
- package/dist/components/trace-builtin.mjs.map +1 -1
- package/dist/components/trace-builtin.test.cjs +16 -0
- package/dist/components/trace-builtin.test.d.cts +1 -0
- package/dist/components/trace-builtin.test.d.mts +1 -0
- package/dist/components/trace-builtin.test.mjs +18 -0
- package/dist/components/trace-builtin.test.mjs.map +1 -0
- package/dist/helpers/automd-generator.cjs +8 -0
- package/dist/helpers/automd-generator.mjs +3 -1
- package/dist/helpers/automd-generator.mjs.map +1 -1
- package/dist/helpers/automd-generator.test.cjs +16 -0
- package/dist/helpers/automd-generator.test.d.cts +1 -0
- package/dist/helpers/automd-generator.test.d.mts +1 -0
- package/dist/helpers/automd-generator.test.mjs +18 -0
- package/dist/helpers/automd-generator.test.mjs.map +1 -0
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +2 -2
- package/dist/index.test.cjs +30 -0
- package/dist/index.test.d.cts +1 -0
- package/dist/index.test.d.mts +1 -0
- package/dist/index.test.mjs +32 -0
- package/dist/index.test.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.cjs +78 -0
- package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs +78 -0
- package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_expect@4.1.7/node_modules/@vitest/expect/dist/index.cjs +1487 -0
- package/dist/node_modules/.pnpm/@vitest_expect@4.1.7/node_modules/@vitest/expect/dist/index.mjs +1473 -0
- package/dist/node_modules/.pnpm/@vitest_expect@4.1.7/node_modules/@vitest/expect/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.7/node_modules/@vitest/pretty-format/dist/index.cjs +889 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.7/node_modules/@vitest/pretty-format/dist/index.mjs +888 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.7/node_modules/@vitest/pretty-format/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/chunk-artifact.cjs +1614 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/chunk-artifact.mjs +1594 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/chunk-artifact.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/index.cjs +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/index.mjs +3 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/utils.cjs +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@4.1.7/node_modules/@vitest/runner/dist/utils.mjs +3 -0
- package/dist/node_modules/.pnpm/@vitest_snapshot@4.1.7/node_modules/@vitest/snapshot/dist/index.cjs +923 -0
- package/dist/node_modules/.pnpm/@vitest_snapshot@4.1.7/node_modules/@vitest/snapshot/dist/index.mjs +922 -0
- package/dist/node_modules/.pnpm/@vitest_snapshot@4.1.7/node_modules/@vitest/snapshot/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_spy@4.1.7/node_modules/@vitest/spy/dist/index.cjs +391 -0
- package/dist/node_modules/.pnpm/@vitest_spy@4.1.7/node_modules/@vitest/spy/dist/index.mjs +386 -0
- package/dist/node_modules/.pnpm/@vitest_spy@4.1.7/node_modules/@vitest/spy/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.cjs +82 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs +82 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/diff.cjs +1358 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/diff.mjs +1357 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/diff.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/display.cjs +561 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/display.mjs +559 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/display.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/error.cjs +37 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/error.mjs +38 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/error.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/helpers.cjs +197 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/helpers.mjs +181 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/helpers.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/offset.cjs +29 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/offset.mjs +27 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/offset.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/serialize.cjs +77 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/serialize.mjs +77 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/serialize.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/source-map.cjs +374 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/source-map.mjs +374 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/source-map.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/timers.cjs +38 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/timers.mjs +37 -0
- package/dist/node_modules/.pnpm/@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/timers.mjs.map +1 -0
- package/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.cjs +2978 -0
- package/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.mjs +2973 -0
- package/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.cjs +939 -0
- package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs +940 -0
- package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.cjs +87 -0
- package/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.mjs +87 -0
- package/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.cjs +6 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs +6 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs.map +1 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/rpc.MzXet3jl.cjs +54 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs +52 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs.map +1 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/test.DNmyFkvJ.cjs +2696 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs +2697 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs.map +1 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.cjs +47 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs +45 -0
- package/dist/node_modules/.pnpm/vitest@4.1.7_@opentelemetry_api@1.9.1_@types_node@25.9.1_@vitest_coverage-v8@4.1.7_vite_34dd47607c0af5e5ae7f1b2f95fc48c3/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs.map +1 -0
- package/package.json +70 -15
|
@@ -0,0 +1,1614 @@
|
|
|
1
|
+
const require_display = require('../../../../../@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/display.cjs');
|
|
2
|
+
const require_helpers = require('../../../../../@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/helpers.cjs');
|
|
3
|
+
const require_timers = require('../../../../../@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/timers.cjs');
|
|
4
|
+
const require_source_map = require('../../../../../@vitest_utils@4.1.7/node_modules/@vitest/utils/dist/source-map.cjs');
|
|
5
|
+
|
|
6
|
+
//#region ../../../node_modules/.pnpm/@vitest+runner@4.1.7/node_modules/@vitest/runner/dist/chunk-artifact.js
|
|
7
|
+
var PendingError = class extends Error {
|
|
8
|
+
code = "VITEST_PENDING";
|
|
9
|
+
taskId;
|
|
10
|
+
constructor(message, task, note) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.message = message;
|
|
13
|
+
this.note = note;
|
|
14
|
+
this.taskId = task.id;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var FixtureDependencyError = class extends Error {
|
|
18
|
+
name = "FixtureDependencyError";
|
|
19
|
+
};
|
|
20
|
+
var FixtureAccessError = class extends Error {
|
|
21
|
+
name = "FixtureAccessError";
|
|
22
|
+
};
|
|
23
|
+
var FixtureParseError = class extends Error {
|
|
24
|
+
name = "FixtureParseError";
|
|
25
|
+
};
|
|
26
|
+
const fnMap = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
const testFixtureMap = /* @__PURE__ */ new WeakMap();
|
|
28
|
+
const hooksMap = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
function setFn(key, fn) {
|
|
30
|
+
fnMap.set(key, fn);
|
|
31
|
+
}
|
|
32
|
+
function setTestFixture(key, fixture) {
|
|
33
|
+
testFixtureMap.set(key, fixture);
|
|
34
|
+
}
|
|
35
|
+
function getTestFixtures(key) {
|
|
36
|
+
return testFixtureMap.get(key);
|
|
37
|
+
}
|
|
38
|
+
function setHooks(key, hooks) {
|
|
39
|
+
hooksMap.set(key, hooks);
|
|
40
|
+
}
|
|
41
|
+
function getHooks(key) {
|
|
42
|
+
return hooksMap.get(key);
|
|
43
|
+
}
|
|
44
|
+
const FIXTURE_STACK_TRACE_KEY = Symbol.for("VITEST_FIXTURE_STACK_TRACE");
|
|
45
|
+
var TestFixtures = class TestFixtures {
|
|
46
|
+
_suiteContexts;
|
|
47
|
+
_overrides = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
_registrations;
|
|
49
|
+
static _definitions = [];
|
|
50
|
+
static _builtinFixtures = [
|
|
51
|
+
"task",
|
|
52
|
+
"signal",
|
|
53
|
+
"onTestFailed",
|
|
54
|
+
"onTestFinished",
|
|
55
|
+
"skip",
|
|
56
|
+
"annotate"
|
|
57
|
+
];
|
|
58
|
+
static _fixtureOptionKeys = [
|
|
59
|
+
"auto",
|
|
60
|
+
"injected",
|
|
61
|
+
"scope"
|
|
62
|
+
];
|
|
63
|
+
static _fixtureScopes = [
|
|
64
|
+
"test",
|
|
65
|
+
"file",
|
|
66
|
+
"worker"
|
|
67
|
+
];
|
|
68
|
+
static _workerContextSuite = { type: "worker" };
|
|
69
|
+
static clearDefinitions() {
|
|
70
|
+
TestFixtures._definitions.length = 0;
|
|
71
|
+
}
|
|
72
|
+
static getWorkerContexts() {
|
|
73
|
+
return TestFixtures._definitions.map((f) => f.getWorkerContext());
|
|
74
|
+
}
|
|
75
|
+
static getFileContexts(file) {
|
|
76
|
+
return TestFixtures._definitions.map((f) => f.getFileContext(file));
|
|
77
|
+
}
|
|
78
|
+
static isFixtureOptions(obj) {
|
|
79
|
+
return require_helpers.isObject(obj) && Object.keys(obj).some((key) => TestFixtures._fixtureOptionKeys.includes(key));
|
|
80
|
+
}
|
|
81
|
+
constructor(registrations) {
|
|
82
|
+
this._registrations = registrations ?? /* @__PURE__ */ new Map();
|
|
83
|
+
this._suiteContexts = /* @__PURE__ */ new WeakMap();
|
|
84
|
+
TestFixtures._definitions.push(this);
|
|
85
|
+
}
|
|
86
|
+
extend(runner, userFixtures) {
|
|
87
|
+
const { suite } = getCurrentSuite();
|
|
88
|
+
const isTopLevel = !suite || suite.file === suite;
|
|
89
|
+
return new TestFixtures(this.parseUserFixtures(runner, userFixtures, isTopLevel));
|
|
90
|
+
}
|
|
91
|
+
get(suite) {
|
|
92
|
+
let currentSuite = suite;
|
|
93
|
+
while (currentSuite) {
|
|
94
|
+
const overrides = this._overrides.get(currentSuite);
|
|
95
|
+
if (overrides) return overrides;
|
|
96
|
+
if (currentSuite === currentSuite.file) break;
|
|
97
|
+
currentSuite = currentSuite.suite || currentSuite.file;
|
|
98
|
+
}
|
|
99
|
+
return this._registrations;
|
|
100
|
+
}
|
|
101
|
+
override(runner, userFixtures) {
|
|
102
|
+
const { suite: currentSuite, file } = getCurrentSuite();
|
|
103
|
+
const suite = currentSuite || file;
|
|
104
|
+
const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
|
|
105
|
+
const suiteRegistrations = new Map(this.get(suite));
|
|
106
|
+
const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
|
|
107
|
+
if (isTopLevel) this._registrations = registrations;
|
|
108
|
+
else this._overrides.set(suite, registrations);
|
|
109
|
+
}
|
|
110
|
+
getFileContext(file) {
|
|
111
|
+
if (!this._suiteContexts.has(file)) this._suiteContexts.set(file, Object.create(null));
|
|
112
|
+
return this._suiteContexts.get(file);
|
|
113
|
+
}
|
|
114
|
+
getWorkerContext() {
|
|
115
|
+
if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
|
|
116
|
+
return this._suiteContexts.get(TestFixtures._workerContextSuite);
|
|
117
|
+
}
|
|
118
|
+
parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
|
|
119
|
+
const errors = [];
|
|
120
|
+
Object.entries(userFixtures).forEach(([name, fn]) => {
|
|
121
|
+
let options;
|
|
122
|
+
let value;
|
|
123
|
+
let _options;
|
|
124
|
+
if (Array.isArray(fn) && fn.length >= 2 && TestFixtures.isFixtureOptions(fn[1])) {
|
|
125
|
+
_options = fn[1];
|
|
126
|
+
options = {
|
|
127
|
+
auto: _options.auto ?? false,
|
|
128
|
+
scope: _options.scope ?? "test",
|
|
129
|
+
injected: _options.injected ?? false
|
|
130
|
+
};
|
|
131
|
+
value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
|
|
132
|
+
} else value = fn;
|
|
133
|
+
const parent = registrations.get(name);
|
|
134
|
+
if (parent && options) {
|
|
135
|
+
if (parent.scope !== options.scope) errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
|
|
136
|
+
if (parent.auto !== options.auto) errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
|
|
137
|
+
} else if (parent) options = {
|
|
138
|
+
auto: parent.auto,
|
|
139
|
+
scope: parent.scope,
|
|
140
|
+
injected: parent.injected
|
|
141
|
+
};
|
|
142
|
+
else if (!options) options = {
|
|
143
|
+
auto: false,
|
|
144
|
+
injected: false,
|
|
145
|
+
scope: "test"
|
|
146
|
+
};
|
|
147
|
+
if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
|
|
148
|
+
if (!supportNonTest && options.scope !== "test") errors.push(new FixtureDependencyError(`The "${name}" fixture cannot be defined with a ${options.scope} scope${!_options?.scope && parent?.scope ? " (inherited from the base fixture)" : ""} inside the describe block. Define it at the top level of the file instead.`));
|
|
149
|
+
const deps = isFixtureFunction(value) ? getUsedProps(value) : /* @__PURE__ */ new Set();
|
|
150
|
+
const item = {
|
|
151
|
+
name,
|
|
152
|
+
value,
|
|
153
|
+
auto: options.auto ?? false,
|
|
154
|
+
injected: options.injected ?? false,
|
|
155
|
+
scope: options.scope ?? "test",
|
|
156
|
+
deps,
|
|
157
|
+
parent
|
|
158
|
+
};
|
|
159
|
+
if (isFixtureFunction(value)) Object.assign(value, { [FIXTURE_STACK_TRACE_KEY]: /* @__PURE__ */ new Error("STACK_TRACE_ERROR") });
|
|
160
|
+
registrations.set(name, item);
|
|
161
|
+
if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) item.scope = "file";
|
|
162
|
+
});
|
|
163
|
+
for (const fixture of registrations.values()) for (const depName of fixture.deps) {
|
|
164
|
+
if (TestFixtures._builtinFixtures.includes(depName)) continue;
|
|
165
|
+
const dep = registrations.get(depName);
|
|
166
|
+
if (!dep) {
|
|
167
|
+
errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (depName === fixture.name && !fixture.parent) {
|
|
171
|
+
errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
|
|
175
|
+
errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (errors.length === 1) throw errors[0];
|
|
180
|
+
else if (errors.length > 1) throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
|
|
181
|
+
return registrations;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const cleanupFnArrayMap = /* @__PURE__ */ new WeakMap();
|
|
185
|
+
const contextHasFixturesCache = /* @__PURE__ */ new WeakMap();
|
|
186
|
+
function withFixtures(fn, options) {
|
|
187
|
+
const collector = getCurrentSuite();
|
|
188
|
+
const suite = options?.suite || collector.suite || collector.file;
|
|
189
|
+
return async (hookContext) => {
|
|
190
|
+
const context = hookContext || options?.context;
|
|
191
|
+
if (!context) {
|
|
192
|
+
if (options?.suiteHook) validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
|
|
193
|
+
return fn({});
|
|
194
|
+
}
|
|
195
|
+
const fixtures = options?.fixtures || getTestFixtures(context);
|
|
196
|
+
if (!fixtures) return fn(context);
|
|
197
|
+
const registrations = fixtures.get(suite);
|
|
198
|
+
if (!registrations.size) return fn(context);
|
|
199
|
+
const usedFixtures = [];
|
|
200
|
+
const usedProps = getUsedProps(fn);
|
|
201
|
+
for (const fixture of registrations.values()) if (isAutoFixture(fixture, options) || usedProps.has(fixture.name)) usedFixtures.push(fixture);
|
|
202
|
+
if (!usedFixtures.length) return fn(context);
|
|
203
|
+
if (!cleanupFnArrayMap.has(context)) cleanupFnArrayMap.set(context, []);
|
|
204
|
+
const cleanupFnArray = cleanupFnArrayMap.get(context);
|
|
205
|
+
const pendingFixtures = resolveDeps(usedFixtures, registrations);
|
|
206
|
+
if (!pendingFixtures.length) return fn(context);
|
|
207
|
+
if (options?.suiteHook) {
|
|
208
|
+
const testScopedFixtures = pendingFixtures.filter((f) => f.scope === "test");
|
|
209
|
+
if (testScopedFixtures.length > 0) {
|
|
210
|
+
const fixtureNames = testScopedFixtures.map((f) => `"${f.name}"`).join(", ");
|
|
211
|
+
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 ${{
|
|
212
|
+
aroundAll: "aroundEach",
|
|
213
|
+
beforeAll: "beforeEach",
|
|
214
|
+
afterAll: "afterEach"
|
|
215
|
+
}[options.suiteHook]} hook.`);
|
|
216
|
+
if (options.stackTraceError?.stack) error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!contextHasFixturesCache.has(context)) contextHasFixturesCache.set(context, /* @__PURE__ */ new WeakSet());
|
|
221
|
+
const cachedFixtures = contextHasFixturesCache.get(context);
|
|
222
|
+
for (const fixture of pendingFixtures) if (fixture.scope === "test") {
|
|
223
|
+
if (cachedFixtures.has(fixture)) continue;
|
|
224
|
+
cachedFixtures.add(fixture);
|
|
225
|
+
const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
|
|
226
|
+
context[fixture.name] = resolvedValue;
|
|
227
|
+
cleanupFnArray.push(() => {
|
|
228
|
+
cachedFixtures.delete(fixture);
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
|
|
232
|
+
context[fixture.name] = resolvedValue;
|
|
233
|
+
}
|
|
234
|
+
return fn(context);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function isAutoFixture(fixture, options) {
|
|
238
|
+
if (!fixture.auto) return false;
|
|
239
|
+
if (options?.suiteHook && fixture.scope === "test") return false;
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
function isFixtureFunction(value) {
|
|
243
|
+
return typeof value === "function";
|
|
244
|
+
}
|
|
245
|
+
function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
|
|
246
|
+
if (!isFixtureFunction(fixture.value)) return fixture.value;
|
|
247
|
+
return resolveFixtureFunction(fixture.value, fixture.name, context, cleanupFnArray);
|
|
248
|
+
}
|
|
249
|
+
const scopedFixturePromiseCache = /* @__PURE__ */ new WeakMap();
|
|
250
|
+
async function resolveScopeFixtureValue(fixtures, suite, fixture) {
|
|
251
|
+
const workerContext = fixtures.getWorkerContext();
|
|
252
|
+
const fileContext = fixtures.getFileContext(suite.file);
|
|
253
|
+
const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
|
|
254
|
+
if (!isFixtureFunction(fixture.value)) {
|
|
255
|
+
fixtureContext[fixture.name] = fixture.value;
|
|
256
|
+
return fixture.value;
|
|
257
|
+
}
|
|
258
|
+
if (fixture.name in fixtureContext) return fixtureContext[fixture.name];
|
|
259
|
+
if (scopedFixturePromiseCache.has(fixture)) return scopedFixturePromiseCache.get(fixture);
|
|
260
|
+
if (!cleanupFnArrayMap.has(fixtureContext)) cleanupFnArrayMap.set(fixtureContext, []);
|
|
261
|
+
const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
|
|
262
|
+
const promise = resolveFixtureFunction(fixture.value, fixture.name, fixture.scope === "file" ? {
|
|
263
|
+
...workerContext,
|
|
264
|
+
...fileContext
|
|
265
|
+
} : fixtureContext, cleanupFnFileArray).then((value) => {
|
|
266
|
+
fixtureContext[fixture.name] = value;
|
|
267
|
+
scopedFixturePromiseCache.delete(fixture);
|
|
268
|
+
return value;
|
|
269
|
+
});
|
|
270
|
+
scopedFixturePromiseCache.set(fixture, promise);
|
|
271
|
+
return promise;
|
|
272
|
+
}
|
|
273
|
+
async function resolveFixtureFunction(fixtureFn, fixtureName, context, cleanupFnArray) {
|
|
274
|
+
const useFnArgPromise = require_helpers.createDefer();
|
|
275
|
+
const stackTraceError = FIXTURE_STACK_TRACE_KEY in fixtureFn && fixtureFn[FIXTURE_STACK_TRACE_KEY] instanceof Error ? fixtureFn[FIXTURE_STACK_TRACE_KEY] : void 0;
|
|
276
|
+
let isUseFnArgResolved = false;
|
|
277
|
+
const fixtureReturn = fixtureFn(context, async (useFnArg) => {
|
|
278
|
+
isUseFnArgResolved = true;
|
|
279
|
+
useFnArgPromise.resolve(useFnArg);
|
|
280
|
+
const useReturnPromise = require_helpers.createDefer();
|
|
281
|
+
cleanupFnArray.push(async () => {
|
|
282
|
+
useReturnPromise.resolve();
|
|
283
|
+
await fixtureReturn;
|
|
284
|
+
});
|
|
285
|
+
await useReturnPromise;
|
|
286
|
+
}).then(() => {
|
|
287
|
+
if (!isUseFnArgResolved) {
|
|
288
|
+
const error = /* @__PURE__ */ new Error(`Fixture "${fixtureName}" returned without calling "use". Make sure to call "use" in every code path of the fixture function.`);
|
|
289
|
+
if (stackTraceError?.stack) error.stack = error.message + stackTraceError.stack.replace(stackTraceError.message, "");
|
|
290
|
+
useFnArgPromise.reject(error);
|
|
291
|
+
}
|
|
292
|
+
}).catch((e) => {
|
|
293
|
+
if (!isUseFnArgResolved) {
|
|
294
|
+
useFnArgPromise.reject(e);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
throw e;
|
|
298
|
+
});
|
|
299
|
+
return useFnArgPromise;
|
|
300
|
+
}
|
|
301
|
+
function resolveDeps(usedFixtures, registrations, depSet = /* @__PURE__ */ new Set(), pendingFixtures = []) {
|
|
302
|
+
usedFixtures.forEach((fixture) => {
|
|
303
|
+
if (pendingFixtures.includes(fixture)) return;
|
|
304
|
+
if (!isFixtureFunction(fixture.value) || !fixture.deps) {
|
|
305
|
+
pendingFixtures.push(fixture);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (depSet.has(fixture)) if (fixture.parent) fixture = fixture.parent;
|
|
309
|
+
else throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map((d) => d.name).join(" <- ")}`);
|
|
310
|
+
depSet.add(fixture);
|
|
311
|
+
resolveDeps([...fixture.deps].map((n) => n === fixture.name ? fixture.parent : registrations.get(n)).filter((n) => !!n), registrations, depSet, pendingFixtures);
|
|
312
|
+
pendingFixtures.push(fixture);
|
|
313
|
+
depSet.clear();
|
|
314
|
+
});
|
|
315
|
+
return pendingFixtures;
|
|
316
|
+
}
|
|
317
|
+
function validateSuiteHook(fn, hook, suiteError) {
|
|
318
|
+
const usedProps = getUsedProps(fn, {
|
|
319
|
+
sourceError: suiteError,
|
|
320
|
+
suiteHook: hook
|
|
321
|
+
});
|
|
322
|
+
if (usedProps.size) {
|
|
323
|
+
const error = new FixtureAccessError(`The ${hook} hook uses fixtures "${[...usedProps].join("\", \"")}", but has no access to context. Did you forget to call it as "test.${hook}()" instead of "${hook}()"?\nIf you used internal "suite" task as the first argument previously, access it in the second argument instead. See https://vitest.dev/guide/test-context#suite-level-hooks`);
|
|
324
|
+
if (suiteError) error.stack = suiteError.stack?.replace(suiteError.message, error.message);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const kPropsSymbol = Symbol("$vitest:fixture-props");
|
|
329
|
+
const kPropNamesSymbol = Symbol("$vitest:fixture-prop-names");
|
|
330
|
+
function configureProps(fn, options) {
|
|
331
|
+
Object.defineProperty(fn, kPropsSymbol, {
|
|
332
|
+
value: options,
|
|
333
|
+
enumerable: false
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function memoProps(fn, props) {
|
|
337
|
+
fn[kPropNamesSymbol] = props;
|
|
338
|
+
return props;
|
|
339
|
+
}
|
|
340
|
+
function getUsedProps(fn, { sourceError, suiteHook } = {}) {
|
|
341
|
+
if (kPropNamesSymbol in fn) return fn[kPropNamesSymbol];
|
|
342
|
+
const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
|
|
343
|
+
let fnString = require_helpers.filterOutComments(implementation.toString());
|
|
344
|
+
if (/__async\((?:this|null), (?:null|arguments|\[[_0-9, ]*\]), function\*/.test(fnString)) fnString = fnString.split(/__async\((?:this|null),/)[1];
|
|
345
|
+
const match = fnString.match(/[^(]*\(([^)]*)/);
|
|
346
|
+
if (!match) return memoProps(fn, /* @__PURE__ */ new Set());
|
|
347
|
+
const args = splitByComma(match[1]);
|
|
348
|
+
if (!args.length) return memoProps(fn, /* @__PURE__ */ new Set());
|
|
349
|
+
const fixturesArgument = args[fixturesIndex];
|
|
350
|
+
if (!fixturesArgument) return memoProps(fn, /* @__PURE__ */ new Set());
|
|
351
|
+
if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
|
|
352
|
+
const ordinalArgument = require_helpers.ordinal(fixturesIndex + 1);
|
|
353
|
+
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 ${require_helpers.ordinal(fixturesIndex + 2)} argument instead.` : ""}`);
|
|
354
|
+
if (sourceError) error.stack = sourceError.stack?.replace(sourceError.message, error.message);
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
const props = splitByComma(fixturesArgument.slice(1, -1).replace(/\s/g, "")).map((prop) => {
|
|
358
|
+
return prop.replace(/:.*|=.*/g, "");
|
|
359
|
+
});
|
|
360
|
+
const last = props.at(-1);
|
|
361
|
+
if (last && last.startsWith("...")) {
|
|
362
|
+
const error = new FixtureParseError(`Rest parameters are not supported in fixtures, received "${last}".`);
|
|
363
|
+
if (sourceError) error.stack = sourceError.stack?.replace(sourceError.message, error.message);
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
return memoProps(fn, new Set(props));
|
|
367
|
+
}
|
|
368
|
+
function splitByComma(s) {
|
|
369
|
+
const result = [];
|
|
370
|
+
const stack = [];
|
|
371
|
+
let start = 0;
|
|
372
|
+
for (let i = 0; i < s.length; i++) if (s[i] === "{" || s[i] === "[") stack.push(s[i] === "{" ? "}" : "]");
|
|
373
|
+
else if (s[i] === stack.at(-1)) stack.pop();
|
|
374
|
+
else if (!stack.length && s[i] === ",") {
|
|
375
|
+
const token = s.substring(start, i).trim();
|
|
376
|
+
if (token) result.push(token);
|
|
377
|
+
start = i + 1;
|
|
378
|
+
}
|
|
379
|
+
const lastToken = s.substring(start).trim();
|
|
380
|
+
if (lastToken) result.push(lastToken);
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
let _test;
|
|
384
|
+
function getCurrentTest() {
|
|
385
|
+
return _test;
|
|
386
|
+
}
|
|
387
|
+
const kChainableContext = Symbol("kChainableContext");
|
|
388
|
+
function getChainableContext(chainable) {
|
|
389
|
+
return chainable?.[kChainableContext];
|
|
390
|
+
}
|
|
391
|
+
function createChainable(keys, fn, context) {
|
|
392
|
+
function create(context) {
|
|
393
|
+
const chain = function(...args) {
|
|
394
|
+
return fn.apply(context, args);
|
|
395
|
+
};
|
|
396
|
+
Object.assign(chain, fn);
|
|
397
|
+
Object.defineProperty(chain, kChainableContext, {
|
|
398
|
+
value: {
|
|
399
|
+
withContext: () => chain.bind(context),
|
|
400
|
+
getFixtures: () => context.fixtures,
|
|
401
|
+
setContext: (key, value) => {
|
|
402
|
+
context[key] = value;
|
|
403
|
+
},
|
|
404
|
+
mergeContext: (ctx) => {
|
|
405
|
+
Object.assign(context, ctx);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
enumerable: false
|
|
409
|
+
});
|
|
410
|
+
for (const key of keys) Object.defineProperty(chain, key, { get() {
|
|
411
|
+
return create({
|
|
412
|
+
...context,
|
|
413
|
+
[key]: true
|
|
414
|
+
});
|
|
415
|
+
} });
|
|
416
|
+
return chain;
|
|
417
|
+
}
|
|
418
|
+
const chain = create(context ?? {});
|
|
419
|
+
Object.defineProperty(chain, "fn", {
|
|
420
|
+
value: fn,
|
|
421
|
+
enumerable: false
|
|
422
|
+
});
|
|
423
|
+
return chain;
|
|
424
|
+
}
|
|
425
|
+
function getDefaultHookTimeout() {
|
|
426
|
+
return getRunner().config.hookTimeout;
|
|
427
|
+
}
|
|
428
|
+
const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
|
|
429
|
+
const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
|
|
430
|
+
const AROUND_TIMEOUT_KEY = Symbol.for("VITEST_AROUND_TIMEOUT");
|
|
431
|
+
const AROUND_STACK_TRACE_KEY = Symbol.for("VITEST_AROUND_STACK_TRACE");
|
|
432
|
+
/**
|
|
433
|
+
* Registers a callback function to be executed once before all tests within the current suite.
|
|
434
|
+
* This hook is useful for scenarios where you need to perform setup operations that are common to all tests in a suite, such as initializing a database connection or setting up a test environment.
|
|
435
|
+
*
|
|
436
|
+
* **Note:** The `beforeAll` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
437
|
+
*
|
|
438
|
+
* @param {Function} fn - The callback function to be executed before all tests.
|
|
439
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
440
|
+
* @returns {void}
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* // Example of using beforeAll to set up a database connection
|
|
444
|
+
* beforeAll(async () => {
|
|
445
|
+
* await database.connect();
|
|
446
|
+
* });
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
function beforeAll(fn, timeout = getDefaultHookTimeout()) {
|
|
450
|
+
require_helpers.assertTypes(fn, "\"beforeAll\" callback", ["function"]);
|
|
451
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
452
|
+
const context = getChainableContext(this);
|
|
453
|
+
return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
|
|
454
|
+
[CLEANUP_TIMEOUT_KEY]: timeout,
|
|
455
|
+
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Registers a callback function to be executed once after all tests within the current suite have completed.
|
|
460
|
+
* This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
|
|
461
|
+
*
|
|
462
|
+
* **Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
463
|
+
*
|
|
464
|
+
* @param {Function} fn - The callback function to be executed after all tests.
|
|
465
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
466
|
+
* @returns {void}
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* // Example of using afterAll to close a database connection
|
|
470
|
+
* afterAll(async () => {
|
|
471
|
+
* await database.disconnect();
|
|
472
|
+
* });
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
function afterAll(fn, timeout) {
|
|
476
|
+
require_helpers.assertTypes(fn, "\"afterAll\" callback", ["function"]);
|
|
477
|
+
const context = getChainableContext(this);
|
|
478
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
479
|
+
return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Registers a callback function to be executed before each test within the current suite.
|
|
483
|
+
* This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables.
|
|
484
|
+
*
|
|
485
|
+
* **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
486
|
+
*
|
|
487
|
+
* @param {Function} fn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.
|
|
488
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
489
|
+
* @returns {void}
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* // Example of using beforeEach to reset a database state
|
|
493
|
+
* beforeEach(async () => {
|
|
494
|
+
* await database.reset();
|
|
495
|
+
* });
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
function beforeEach(fn, timeout = getDefaultHookTimeout()) {
|
|
499
|
+
require_helpers.assertTypes(fn, "\"beforeEach\" callback", ["function"]);
|
|
500
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
501
|
+
const wrapper = (context, suite) => {
|
|
502
|
+
return withFixtures(fn, { suite })(context);
|
|
503
|
+
};
|
|
504
|
+
return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
|
|
505
|
+
[CLEANUP_TIMEOUT_KEY]: timeout,
|
|
506
|
+
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Registers a callback function to be executed after each test within the current suite has completed.
|
|
511
|
+
* This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions.
|
|
512
|
+
*
|
|
513
|
+
* **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
514
|
+
*
|
|
515
|
+
* @param {Function} fn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.
|
|
516
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
517
|
+
* @returns {void}
|
|
518
|
+
* @example
|
|
519
|
+
* ```ts
|
|
520
|
+
* // Example of using afterEach to delete temporary files created during a test
|
|
521
|
+
* afterEach(async () => {
|
|
522
|
+
* await fileSystem.deleteTempFiles();
|
|
523
|
+
* });
|
|
524
|
+
* ```
|
|
525
|
+
*/
|
|
526
|
+
function afterEach(fn, timeout) {
|
|
527
|
+
require_helpers.assertTypes(fn, "\"afterEach\" callback", ["function"]);
|
|
528
|
+
const wrapper = (context, suite) => {
|
|
529
|
+
return withFixtures(fn, { suite })(context);
|
|
530
|
+
};
|
|
531
|
+
return getCurrentSuite().on("afterEach", withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Registers a callback function to be executed when a test fails within the current suite.
|
|
535
|
+
* This function allows for custom actions to be performed in response to test failures, such as logging, cleanup, or additional diagnostics.
|
|
536
|
+
*
|
|
537
|
+
* **Note:** The `onTestFailed` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
538
|
+
*
|
|
539
|
+
* @param {Function} fn - The callback function to be executed upon a test failure. The function receives the test result (including errors).
|
|
540
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
541
|
+
* @throws {Error} Throws an error if the function is not called within a test.
|
|
542
|
+
* @returns {void}
|
|
543
|
+
* @example
|
|
544
|
+
* ```ts
|
|
545
|
+
* // Example of using onTestFailed to log failure details
|
|
546
|
+
* onTestFailed(({ errors }) => {
|
|
547
|
+
* console.log(`Test failed: ${test.name}`, errors);
|
|
548
|
+
* });
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
|
|
552
|
+
test.onFailed ||= [];
|
|
553
|
+
test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
554
|
+
});
|
|
555
|
+
/**
|
|
556
|
+
* Registers a callback function to be executed when the current test finishes, regardless of the outcome (pass or fail).
|
|
557
|
+
* This function is ideal for performing actions that should occur after every test execution, such as cleanup, logging, or resetting shared resources.
|
|
558
|
+
*
|
|
559
|
+
* This hook is useful if you have access to a resource in the test itself and you want to clean it up after the test finishes. It is a more compact way to clean up resources than using the combination of `beforeEach` and `afterEach`.
|
|
560
|
+
*
|
|
561
|
+
* **Note:** The `onTestFinished` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
|
|
562
|
+
*
|
|
563
|
+
* **Note:** The `onTestFinished` hook is not called if the test is canceled with a dynamic `ctx.skip()` call.
|
|
564
|
+
*
|
|
565
|
+
* @param {Function} fn - The callback function to be executed after a test finishes. The function can receive parameters providing details about the completed test, including its success or failure status.
|
|
566
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
567
|
+
* @throws {Error} Throws an error if the function is not called within a test.
|
|
568
|
+
* @returns {void}
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* // Example of using onTestFinished for cleanup
|
|
572
|
+
* const db = await connectToDatabase();
|
|
573
|
+
* onTestFinished(async () => {
|
|
574
|
+
* await db.disconnect();
|
|
575
|
+
* });
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
|
|
579
|
+
test.onFinished ||= [];
|
|
580
|
+
test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
581
|
+
});
|
|
582
|
+
/**
|
|
583
|
+
* Registers a callback function that wraps around all tests within the current suite.
|
|
584
|
+
* The callback receives a `runSuite` function that must be called to run the suite's tests.
|
|
585
|
+
* This hook is useful for scenarios where you need to wrap an entire suite in a context
|
|
586
|
+
* (e.g., starting a server, opening a database connection that all tests share).
|
|
587
|
+
*
|
|
588
|
+
* **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
|
|
589
|
+
* The first registered hook is the outermost wrapper.
|
|
590
|
+
*
|
|
591
|
+
* @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
|
|
592
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
593
|
+
* @returns {void}
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* // Example of using aroundAll to wrap suite in a tracing span
|
|
597
|
+
* aroundAll(async (runSuite) => {
|
|
598
|
+
* await tracer.trace('test-suite', runSuite);
|
|
599
|
+
* });
|
|
600
|
+
* ```
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* // Example of using aroundAll with fixtures
|
|
604
|
+
* aroundAll(async (runSuite, { db }) => {
|
|
605
|
+
* await db.transaction(() => runSuite());
|
|
606
|
+
* });
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
function aroundAll(fn, timeout) {
|
|
610
|
+
require_helpers.assertTypes(fn, "\"aroundAll\" callback", ["function"]);
|
|
611
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
612
|
+
const resolvedTimeout = timeout ?? getDefaultHookTimeout();
|
|
613
|
+
const context = getChainableContext(this);
|
|
614
|
+
return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
|
|
615
|
+
[AROUND_TIMEOUT_KEY]: resolvedTimeout,
|
|
616
|
+
[AROUND_STACK_TRACE_KEY]: stackTraceError
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Registers a callback function that wraps around each test within the current suite.
|
|
621
|
+
* The callback receives a `runTest` function that must be called to run the test.
|
|
622
|
+
* This hook is useful for scenarios where you need to wrap tests in a context (e.g., database transactions).
|
|
623
|
+
*
|
|
624
|
+
* **Note:** When multiple `aroundEach` hooks are registered, they are nested inside each other.
|
|
625
|
+
* The first registered hook is the outermost wrapper.
|
|
626
|
+
*
|
|
627
|
+
* @param {Function} fn - The callback function that wraps the test. Must call `runTest()` to run the test.
|
|
628
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
629
|
+
* @returns {void}
|
|
630
|
+
* @example
|
|
631
|
+
* ```ts
|
|
632
|
+
* // Example of using aroundEach to wrap tests in a database transaction
|
|
633
|
+
* aroundEach(async (runTest) => {
|
|
634
|
+
* await database.transaction(() => runTest());
|
|
635
|
+
* });
|
|
636
|
+
* ```
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* // Example of using aroundEach with fixtures
|
|
640
|
+
* aroundEach(async (runTest, { db }) => {
|
|
641
|
+
* await db.transaction(() => runTest());
|
|
642
|
+
* });
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
function aroundEach(fn, timeout) {
|
|
646
|
+
require_helpers.assertTypes(fn, "\"aroundEach\" callback", ["function"]);
|
|
647
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
648
|
+
const resolvedTimeout = timeout ?? getDefaultHookTimeout();
|
|
649
|
+
const wrapper = (runTest, context, suite) => {
|
|
650
|
+
const innerFn = (ctx) => fn(runTest, ctx, suite);
|
|
651
|
+
configureProps(innerFn, {
|
|
652
|
+
index: 1,
|
|
653
|
+
original: fn
|
|
654
|
+
});
|
|
655
|
+
return withFixtures(innerFn, { suite })(context);
|
|
656
|
+
};
|
|
657
|
+
return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
|
|
658
|
+
[AROUND_TIMEOUT_KEY]: resolvedTimeout,
|
|
659
|
+
[AROUND_STACK_TRACE_KEY]: stackTraceError
|
|
660
|
+
}));
|
|
661
|
+
}
|
|
662
|
+
function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
|
|
663
|
+
return (...args) => {
|
|
664
|
+
const suite = args.at(-1);
|
|
665
|
+
const prefix = args.slice(0, -1);
|
|
666
|
+
const wrapper = (ctx) => fn(...prefix, ctx, suite);
|
|
667
|
+
configureProps(wrapper, {
|
|
668
|
+
index: contextIndex,
|
|
669
|
+
original: fn
|
|
670
|
+
});
|
|
671
|
+
const fixtures = context?.getFixtures();
|
|
672
|
+
const fileContext = fixtures?.getFileContext(suite.file);
|
|
673
|
+
return withFixtures(wrapper, {
|
|
674
|
+
suiteHook,
|
|
675
|
+
fixtures,
|
|
676
|
+
context: fileContext,
|
|
677
|
+
stackTraceError
|
|
678
|
+
})();
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function createTestHook(name, handler) {
|
|
682
|
+
return (fn, timeout) => {
|
|
683
|
+
require_helpers.assertTypes(fn, `"${name}" callback`, ["function"]);
|
|
684
|
+
const current = getCurrentTest();
|
|
685
|
+
if (!current) throw new Error(`Hook ${name}() can only be called inside a test`);
|
|
686
|
+
return handler(current, fn, timeout);
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function findTestFileStackTrace(testFilePath, error) {
|
|
690
|
+
const lines = error.split("\n").slice(1);
|
|
691
|
+
for (const line of lines) {
|
|
692
|
+
const stack = require_source_map.parseSingleStack(line);
|
|
693
|
+
if (stack && stack.file === testFilePath) return stack;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function validateTags(config, tags) {
|
|
697
|
+
if (!config.strictTags) return;
|
|
698
|
+
const availableTags = new Set(config.tags.map((tag) => tag.name));
|
|
699
|
+
for (const tag of tags) if (!availableTags.has(tag)) throw createNoTagsError(config.tags, tag);
|
|
700
|
+
}
|
|
701
|
+
function createNoTagsError(availableTags, tag, prefix = "tag") {
|
|
702
|
+
if (!availableTags.length) throw new Error(`The Vitest config does't define any "tags", cannot apply "${tag}" ${prefix} for this test. See: https://vitest.dev/guide/test-tags`);
|
|
703
|
+
throw new Error(`The ${prefix} "${tag}" is not defined in the configuration. Available tags are:\n${availableTags.map((t) => `- ${t.name}${t.description ? `: ${t.description}` : ""}`).join("\n")}`);
|
|
704
|
+
}
|
|
705
|
+
function getNames(task) {
|
|
706
|
+
const names = [task.name];
|
|
707
|
+
let current = task;
|
|
708
|
+
while (current?.suite) {
|
|
709
|
+
current = current.suite;
|
|
710
|
+
if (current?.name) names.unshift(current.name);
|
|
711
|
+
}
|
|
712
|
+
if (current !== task.file) names.unshift(task.file.name);
|
|
713
|
+
return names;
|
|
714
|
+
}
|
|
715
|
+
function createTaskName(names, separator = " > ") {
|
|
716
|
+
return names.filter((name) => name !== void 0).join(separator);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
|
|
720
|
+
* Suites can contain both tests and other suites, enabling complex test structures.
|
|
721
|
+
*
|
|
722
|
+
* @param {string} name - The name of the suite, used for identification and reporting.
|
|
723
|
+
* @param {Function} fn - A function that defines the tests and suites within this suite.
|
|
724
|
+
* @example
|
|
725
|
+
* ```ts
|
|
726
|
+
* // Define a suite with two tests
|
|
727
|
+
* suite('Math operations', () => {
|
|
728
|
+
* test('should add two numbers', () => {
|
|
729
|
+
* expect(add(1, 2)).toBe(3);
|
|
730
|
+
* });
|
|
731
|
+
*
|
|
732
|
+
* test('should subtract two numbers', () => {
|
|
733
|
+
* expect(subtract(5, 2)).toBe(3);
|
|
734
|
+
* });
|
|
735
|
+
* });
|
|
736
|
+
* ```
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* // Define nested suites
|
|
740
|
+
* suite('String operations', () => {
|
|
741
|
+
* suite('Trimming', () => {
|
|
742
|
+
* test('should trim whitespace from start and end', () => {
|
|
743
|
+
* expect(' hello '.trim()).toBe('hello');
|
|
744
|
+
* });
|
|
745
|
+
* });
|
|
746
|
+
*
|
|
747
|
+
* suite('Concatenation', () => {
|
|
748
|
+
* test('should concatenate two strings', () => {
|
|
749
|
+
* expect('hello' + ' ' + 'world').toBe('hello world');
|
|
750
|
+
* });
|
|
751
|
+
* });
|
|
752
|
+
* });
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
const suite = createSuite();
|
|
756
|
+
/**
|
|
757
|
+
* Defines a test case with a given name and test function. The test function can optionally be configured with test options.
|
|
758
|
+
*
|
|
759
|
+
* @param {string | Function} name - The name of the test or a function that will be used as a test name.
|
|
760
|
+
* @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
|
|
761
|
+
* @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
|
|
762
|
+
* @throws {Error} If called inside another test function.
|
|
763
|
+
* @example
|
|
764
|
+
* ```ts
|
|
765
|
+
* // Define a simple test
|
|
766
|
+
* test('should add two numbers', () => {
|
|
767
|
+
* expect(add(1, 2)).toBe(3);
|
|
768
|
+
* });
|
|
769
|
+
* ```
|
|
770
|
+
* @example
|
|
771
|
+
* ```ts
|
|
772
|
+
* // Define a test with options
|
|
773
|
+
* test('should subtract two numbers', { retry: 3 }, () => {
|
|
774
|
+
* expect(subtract(5, 2)).toBe(3);
|
|
775
|
+
* });
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
778
|
+
const test = createTest(function(name, optionsOrFn, optionsOrTest) {
|
|
779
|
+
if (getCurrentTest()) throw new Error("Calling the test function inside another test function is not allowed. Please put it inside \"describe\" or \"suite\" so it can be properly collected.");
|
|
780
|
+
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
|
|
781
|
+
});
|
|
782
|
+
/**
|
|
783
|
+
* Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
|
|
784
|
+
* Suites can contain both tests and other suites, enabling complex test structures.
|
|
785
|
+
*
|
|
786
|
+
* @param {string} name - The name of the suite, used for identification and reporting.
|
|
787
|
+
* @param {Function} fn - A function that defines the tests and suites within this suite.
|
|
788
|
+
* @example
|
|
789
|
+
* ```ts
|
|
790
|
+
* // Define a suite with two tests
|
|
791
|
+
* describe('Math operations', () => {
|
|
792
|
+
* test('should add two numbers', () => {
|
|
793
|
+
* expect(add(1, 2)).toBe(3);
|
|
794
|
+
* });
|
|
795
|
+
*
|
|
796
|
+
* test('should subtract two numbers', () => {
|
|
797
|
+
* expect(subtract(5, 2)).toBe(3);
|
|
798
|
+
* });
|
|
799
|
+
* });
|
|
800
|
+
* ```
|
|
801
|
+
* @example
|
|
802
|
+
* ```ts
|
|
803
|
+
* // Define nested suites
|
|
804
|
+
* describe('String operations', () => {
|
|
805
|
+
* describe('Trimming', () => {
|
|
806
|
+
* test('should trim whitespace from start and end', () => {
|
|
807
|
+
* expect(' hello '.trim()).toBe('hello');
|
|
808
|
+
* });
|
|
809
|
+
* });
|
|
810
|
+
*
|
|
811
|
+
* describe('Concatenation', () => {
|
|
812
|
+
* test('should concatenate two strings', () => {
|
|
813
|
+
* expect('hello' + ' ' + 'world').toBe('hello world');
|
|
814
|
+
* });
|
|
815
|
+
* });
|
|
816
|
+
* });
|
|
817
|
+
* ```
|
|
818
|
+
*/
|
|
819
|
+
const describe = suite;
|
|
820
|
+
/**
|
|
821
|
+
* Defines a test case with a given name and test function. The test function can optionally be configured with test options.
|
|
822
|
+
*
|
|
823
|
+
* @param {string | Function} name - The name of the test or a function that will be used as a test name.
|
|
824
|
+
* @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
|
|
825
|
+
* @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
|
|
826
|
+
* @throws {Error} If called inside another test function.
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* // Define a simple test
|
|
830
|
+
* it('adds two numbers', () => {
|
|
831
|
+
* expect(add(1, 2)).toBe(3);
|
|
832
|
+
* });
|
|
833
|
+
* ```
|
|
834
|
+
* @example
|
|
835
|
+
* ```ts
|
|
836
|
+
* // Define a test with options
|
|
837
|
+
* it('subtracts two numbers', { retry: 3 }, () => {
|
|
838
|
+
* expect(subtract(5, 2)).toBe(3);
|
|
839
|
+
* });
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
const it = test;
|
|
843
|
+
let runner;
|
|
844
|
+
let defaultSuite;
|
|
845
|
+
let currentTestFilepath;
|
|
846
|
+
function assert(condition, message) {
|
|
847
|
+
if (!condition) throw new Error(`Vitest failed to find ${message}. One of the following is possible:
|
|
848
|
+
- "vitest" is imported directly without running "vitest" command
|
|
849
|
+
- "vitest" is imported inside "globalSetup" (to fix this, use "setupFiles" instead, because "globalSetup" runs in a different context)
|
|
850
|
+
- "vitest" is imported inside Vite / Vitest config file
|
|
851
|
+
- Otherwise, it might be a Vitest bug. Please report it to https://github.com/vitest-dev/vitest/issues
|
|
852
|
+
`);
|
|
853
|
+
}
|
|
854
|
+
function getRunner() {
|
|
855
|
+
assert(runner, "the runner");
|
|
856
|
+
return runner;
|
|
857
|
+
}
|
|
858
|
+
function getCurrentSuite() {
|
|
859
|
+
const currentSuite = collectorContext.currentSuite || defaultSuite;
|
|
860
|
+
assert(currentSuite, "the current suite");
|
|
861
|
+
return currentSuite;
|
|
862
|
+
}
|
|
863
|
+
function createSuiteHooks() {
|
|
864
|
+
return {
|
|
865
|
+
beforeAll: [],
|
|
866
|
+
afterAll: [],
|
|
867
|
+
beforeEach: [],
|
|
868
|
+
afterEach: [],
|
|
869
|
+
aroundEach: [],
|
|
870
|
+
aroundAll: []
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
|
|
874
|
+
function parseArguments(optionsOrFn, timeoutOrTest) {
|
|
875
|
+
if (timeoutOrTest != null && typeof timeoutOrTest === "object") throw new TypeError(`Signature "test(name, fn, { ... })" was deprecated in Vitest 3 and removed in Vitest 4. Please, provide options as a second argument instead.`);
|
|
876
|
+
let options = {};
|
|
877
|
+
let fn;
|
|
878
|
+
if (typeof timeoutOrTest === "number") options = { timeout: timeoutOrTest };
|
|
879
|
+
else if (typeof optionsOrFn === "object") options = optionsOrFn;
|
|
880
|
+
if (typeof optionsOrFn === "function") {
|
|
881
|
+
if (typeof timeoutOrTest === "function") throw new TypeError("Cannot use two functions as arguments. Please use the second argument for options.");
|
|
882
|
+
fn = optionsOrFn;
|
|
883
|
+
} else if (typeof timeoutOrTest === "function") fn = timeoutOrTest;
|
|
884
|
+
return {
|
|
885
|
+
options,
|
|
886
|
+
handler: fn
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
|
|
890
|
+
const tasks = [];
|
|
891
|
+
let suite;
|
|
892
|
+
initSuite(true);
|
|
893
|
+
const task = function(name = "", options = {}) {
|
|
894
|
+
const currentSuite = collectorContext.currentSuite?.suite;
|
|
895
|
+
const testTags = require_helpers.unique([...(currentSuite ?? collectorContext.currentSuite?.file)?.tags || [], ...require_helpers.toArray(options.tags)]);
|
|
896
|
+
const tagsOptions = testTags.map((tag) => {
|
|
897
|
+
const tagDefinition = runner.config.tags?.find((t) => t.name === tag);
|
|
898
|
+
if (!tagDefinition && runner.config.strictTags) throw createNoTagsError(runner.config.tags, tag);
|
|
899
|
+
return tagDefinition;
|
|
900
|
+
}).filter((r) => r != null).sort((tag1, tag2) => (tag2.priority ?? POSITIVE_INFINITY) - (tag1.priority ?? POSITIVE_INFINITY)).reduce((acc, tag) => {
|
|
901
|
+
const { name, description, priority, meta, ...options } = tag;
|
|
902
|
+
Object.assign(acc, options);
|
|
903
|
+
if (meta) acc.meta = Object.assign(acc.meta ?? Object.create(null), meta);
|
|
904
|
+
return acc;
|
|
905
|
+
}, {});
|
|
906
|
+
const testOwnMeta = options.meta;
|
|
907
|
+
options = {
|
|
908
|
+
...tagsOptions,
|
|
909
|
+
...options
|
|
910
|
+
};
|
|
911
|
+
const timeout = options.timeout ?? runner.config.testTimeout;
|
|
912
|
+
const parentMeta = currentSuite?.meta;
|
|
913
|
+
const tagMeta = tagsOptions.meta;
|
|
914
|
+
const testMeta = Object.create(null);
|
|
915
|
+
if (tagMeta) Object.assign(testMeta, tagMeta);
|
|
916
|
+
if (parentMeta) Object.assign(testMeta, parentMeta);
|
|
917
|
+
if (testOwnMeta) Object.assign(testMeta, testOwnMeta);
|
|
918
|
+
const task = {
|
|
919
|
+
id: "",
|
|
920
|
+
name,
|
|
921
|
+
fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
|
|
922
|
+
fullTestName: createTaskName([currentSuite?.fullTestName, name]),
|
|
923
|
+
suite: currentSuite,
|
|
924
|
+
each: options.each,
|
|
925
|
+
fails: options.fails,
|
|
926
|
+
context: void 0,
|
|
927
|
+
type: "test",
|
|
928
|
+
file: currentSuite?.file ?? collectorContext.currentSuite?.file,
|
|
929
|
+
timeout,
|
|
930
|
+
retry: options.retry ?? runner.config.retry,
|
|
931
|
+
repeats: options.repeats,
|
|
932
|
+
mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
|
|
933
|
+
meta: testMeta,
|
|
934
|
+
annotations: [],
|
|
935
|
+
artifacts: [],
|
|
936
|
+
tags: testTags
|
|
937
|
+
};
|
|
938
|
+
const handler = options.handler;
|
|
939
|
+
if (task.mode === "run" && !handler) task.mode = "todo";
|
|
940
|
+
if (options.concurrent ?? (!options.sequential && runner.config.sequence.concurrent)) task.concurrent = true;
|
|
941
|
+
task.shuffle = suiteOptions?.shuffle;
|
|
942
|
+
const context = createTestContext(task, runner);
|
|
943
|
+
Object.defineProperty(task, "context", {
|
|
944
|
+
value: context,
|
|
945
|
+
enumerable: false
|
|
946
|
+
});
|
|
947
|
+
setTestFixture(context, options.fixtures ?? new TestFixtures());
|
|
948
|
+
const limit = Error.stackTraceLimit;
|
|
949
|
+
Error.stackTraceLimit = 10;
|
|
950
|
+
const stackTraceError = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
|
|
951
|
+
Error.stackTraceLimit = limit;
|
|
952
|
+
if (handler) setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, { context }), task), task.context.signal), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
|
|
953
|
+
if (runner.config.includeTaskLocation) {
|
|
954
|
+
const error = stackTraceError.stack;
|
|
955
|
+
const stack = findTestFileStackTrace(currentTestFilepath, error);
|
|
956
|
+
if (stack) task.location = {
|
|
957
|
+
line: stack.line,
|
|
958
|
+
column: stack.column
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
tasks.push(task);
|
|
962
|
+
return task;
|
|
963
|
+
};
|
|
964
|
+
const test = createTest(function(name, optionsOrFn, timeoutOrTest) {
|
|
965
|
+
let { options, handler } = parseArguments(optionsOrFn, timeoutOrTest);
|
|
966
|
+
if (typeof suiteOptions === "object") options = Object.assign({}, suiteOptions, options);
|
|
967
|
+
const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent);
|
|
968
|
+
if (options.concurrent != null && concurrent != null) options.concurrent = concurrent;
|
|
969
|
+
const sequential = this.sequential ?? (!this.concurrent && options?.sequential);
|
|
970
|
+
if (options.sequential != null && sequential != null) options.sequential = sequential;
|
|
971
|
+
const test = task(formatName(name), {
|
|
972
|
+
...this,
|
|
973
|
+
...options,
|
|
974
|
+
handler
|
|
975
|
+
});
|
|
976
|
+
test.type = "test";
|
|
977
|
+
});
|
|
978
|
+
const collector = {
|
|
979
|
+
type: "collector",
|
|
980
|
+
name,
|
|
981
|
+
mode,
|
|
982
|
+
suite,
|
|
983
|
+
options: suiteOptions,
|
|
984
|
+
test,
|
|
985
|
+
file: suite.file,
|
|
986
|
+
tasks,
|
|
987
|
+
collect,
|
|
988
|
+
task,
|
|
989
|
+
clear,
|
|
990
|
+
on: addHook
|
|
991
|
+
};
|
|
992
|
+
function addHook(name, ...fn) {
|
|
993
|
+
getHooks(suite)[name].push(...fn);
|
|
994
|
+
}
|
|
995
|
+
function initSuite(includeLocation) {
|
|
996
|
+
if (typeof suiteOptions === "number") suiteOptions = { timeout: suiteOptions };
|
|
997
|
+
const currentSuite = collectorContext.currentSuite?.suite;
|
|
998
|
+
const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
|
|
999
|
+
const suiteTags = require_helpers.toArray(suiteOptions?.tags);
|
|
1000
|
+
validateTags(runner.config, suiteTags);
|
|
1001
|
+
suite = {
|
|
1002
|
+
id: "",
|
|
1003
|
+
type: "suite",
|
|
1004
|
+
name,
|
|
1005
|
+
fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
|
|
1006
|
+
fullTestName: createTaskName([currentSuite?.fullTestName, name]),
|
|
1007
|
+
suite: currentSuite,
|
|
1008
|
+
mode,
|
|
1009
|
+
each,
|
|
1010
|
+
file: currentSuite?.file ?? collectorContext.currentSuite?.file,
|
|
1011
|
+
shuffle: suiteOptions?.shuffle,
|
|
1012
|
+
tasks: [],
|
|
1013
|
+
meta: suiteOptions?.meta ?? Object.create(null),
|
|
1014
|
+
concurrent: suiteOptions?.concurrent,
|
|
1015
|
+
tags: require_helpers.unique([...parentTask?.tags || [], ...suiteTags])
|
|
1016
|
+
};
|
|
1017
|
+
if (runner && includeLocation && runner.config.includeTaskLocation) {
|
|
1018
|
+
const limit = Error.stackTraceLimit;
|
|
1019
|
+
Error.stackTraceLimit = 15;
|
|
1020
|
+
const error = (/* @__PURE__ */ new Error("stacktrace")).stack;
|
|
1021
|
+
Error.stackTraceLimit = limit;
|
|
1022
|
+
const stack = findTestFileStackTrace(currentTestFilepath, error);
|
|
1023
|
+
if (stack) suite.location = {
|
|
1024
|
+
line: stack.line,
|
|
1025
|
+
column: stack.column
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
setHooks(suite, createSuiteHooks());
|
|
1029
|
+
}
|
|
1030
|
+
function clear() {
|
|
1031
|
+
tasks.length = 0;
|
|
1032
|
+
initSuite(false);
|
|
1033
|
+
}
|
|
1034
|
+
async function collect(file) {
|
|
1035
|
+
if (!file) throw new TypeError("File is required to collect tasks.");
|
|
1036
|
+
if (factory) await runWithSuite(collector, () => factory(test));
|
|
1037
|
+
const allChildren = [];
|
|
1038
|
+
for (const i of tasks) allChildren.push(i.type === "collector" ? await i.collect(file) : i);
|
|
1039
|
+
suite.tasks = allChildren;
|
|
1040
|
+
return suite;
|
|
1041
|
+
}
|
|
1042
|
+
collectTask(collector);
|
|
1043
|
+
return collector;
|
|
1044
|
+
}
|
|
1045
|
+
function withAwaitAsyncAssertions(fn, task) {
|
|
1046
|
+
return async (...args) => {
|
|
1047
|
+
const fnResult = await fn(...args);
|
|
1048
|
+
if (task.promises) {
|
|
1049
|
+
const errors = (await Promise.allSettled(task.promises)).map((r) => r.status === "rejected" ? r.reason : void 0).filter(Boolean);
|
|
1050
|
+
if (errors.length) throw errors;
|
|
1051
|
+
}
|
|
1052
|
+
return fnResult;
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function createSuite() {
|
|
1056
|
+
function suiteFn(name, factoryOrOptions, optionsOrFactory) {
|
|
1057
|
+
if (getCurrentTest()) throw new Error("Calling the suite function inside test function is not allowed. It can be only called at the top level or inside another suite function.");
|
|
1058
|
+
const currentSuite = collectorContext.currentSuite || defaultSuite;
|
|
1059
|
+
let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
|
|
1060
|
+
const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
|
|
1061
|
+
const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
|
|
1062
|
+
const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
|
|
1063
|
+
options = {
|
|
1064
|
+
...parentOptions,
|
|
1065
|
+
...options
|
|
1066
|
+
};
|
|
1067
|
+
const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
|
|
1068
|
+
if (shuffle != null) options.shuffle = shuffle;
|
|
1069
|
+
let mode = this.only ?? options.only ? "only" : this.skip ?? options.skip ? "skip" : this.todo ?? options.todo ? "todo" : "run";
|
|
1070
|
+
if (mode === "run" && !factory) mode = "todo";
|
|
1071
|
+
const isConcurrent = isConcurrentSpecified || options.concurrent && !isSequentialSpecified;
|
|
1072
|
+
const isSequential = isSequentialSpecified || options.sequential && !isConcurrentSpecified;
|
|
1073
|
+
if (isConcurrent != null) options.concurrent = isConcurrent && !isSequential;
|
|
1074
|
+
if (isSequential != null) options.sequential = isSequential && !isConcurrent;
|
|
1075
|
+
if (parentMeta) options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
|
|
1076
|
+
return createSuiteCollector(formatName(name), factory, mode, this.each, options);
|
|
1077
|
+
}
|
|
1078
|
+
suiteFn.each = function(cases, ...args) {
|
|
1079
|
+
const context = getChainableContext(this);
|
|
1080
|
+
const suite = context.withContext();
|
|
1081
|
+
context.setContext("each", true);
|
|
1082
|
+
if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
|
|
1083
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
|
1084
|
+
const _name = formatName(name);
|
|
1085
|
+
const arrayOnlyCases = cases.every(Array.isArray);
|
|
1086
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
|
1087
|
+
const fnFirst = typeof optionsOrFn === "function";
|
|
1088
|
+
cases.forEach((i, idx) => {
|
|
1089
|
+
const items = Array.isArray(i) ? i : [i];
|
|
1090
|
+
if (fnFirst) if (arrayOnlyCases) suite(formatTitle(_name, items, idx), handler ? () => handler(...items) : void 0, options.timeout);
|
|
1091
|
+
else suite(formatTitle(_name, items, idx), handler ? () => handler(i) : void 0, options.timeout);
|
|
1092
|
+
else if (arrayOnlyCases) suite(formatTitle(_name, items, idx), options, handler ? () => handler(...items) : void 0);
|
|
1093
|
+
else suite(formatTitle(_name, items, idx), options, handler ? () => handler(i) : void 0);
|
|
1094
|
+
});
|
|
1095
|
+
context.setContext("each", void 0);
|
|
1096
|
+
};
|
|
1097
|
+
};
|
|
1098
|
+
suiteFn.for = function(cases, ...args) {
|
|
1099
|
+
if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
|
|
1100
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
|
1101
|
+
const name_ = formatName(name);
|
|
1102
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
|
1103
|
+
cases.forEach((item, idx) => {
|
|
1104
|
+
suite(formatTitle(name_, require_helpers.toArray(item), idx), options, handler ? () => handler(item) : void 0);
|
|
1105
|
+
});
|
|
1106
|
+
};
|
|
1107
|
+
};
|
|
1108
|
+
suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
|
|
1109
|
+
suiteFn.runIf = (condition) => condition ? suite : suite.skip;
|
|
1110
|
+
return createChainable([
|
|
1111
|
+
"concurrent",
|
|
1112
|
+
"sequential",
|
|
1113
|
+
"shuffle",
|
|
1114
|
+
"skip",
|
|
1115
|
+
"only",
|
|
1116
|
+
"todo"
|
|
1117
|
+
], suiteFn);
|
|
1118
|
+
}
|
|
1119
|
+
function createTaskCollector(fn) {
|
|
1120
|
+
const taskFn = fn;
|
|
1121
|
+
taskFn.each = function(cases, ...args) {
|
|
1122
|
+
const context = getChainableContext(this);
|
|
1123
|
+
const test = context.withContext();
|
|
1124
|
+
context.setContext("each", true);
|
|
1125
|
+
if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
|
|
1126
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
|
1127
|
+
const _name = formatName(name);
|
|
1128
|
+
const arrayOnlyCases = cases.every(Array.isArray);
|
|
1129
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
|
1130
|
+
const fnFirst = typeof optionsOrFn === "function";
|
|
1131
|
+
cases.forEach((i, idx) => {
|
|
1132
|
+
const items = Array.isArray(i) ? i : [i];
|
|
1133
|
+
if (fnFirst) if (arrayOnlyCases) test(formatTitle(_name, items, idx), handler ? () => handler(...items) : void 0, options.timeout);
|
|
1134
|
+
else test(formatTitle(_name, items, idx), handler ? () => handler(i) : void 0, options.timeout);
|
|
1135
|
+
else if (arrayOnlyCases) test(formatTitle(_name, items, idx), options, handler ? () => handler(...items) : void 0);
|
|
1136
|
+
else test(formatTitle(_name, items, idx), options, handler ? () => handler(i) : void 0);
|
|
1137
|
+
});
|
|
1138
|
+
context.setContext("each", void 0);
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1141
|
+
taskFn.for = function(cases, ...args) {
|
|
1142
|
+
const test = getChainableContext(this).withContext();
|
|
1143
|
+
if (Array.isArray(cases) && args.length) cases = formatTemplateString(cases, args);
|
|
1144
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
|
1145
|
+
const _name = formatName(name);
|
|
1146
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
|
1147
|
+
cases.forEach((item, idx) => {
|
|
1148
|
+
const handlerWrapper = handler ? (ctx) => handler(item, ctx) : void 0;
|
|
1149
|
+
if (handlerWrapper) configureProps(handlerWrapper, {
|
|
1150
|
+
index: 1,
|
|
1151
|
+
original: handler
|
|
1152
|
+
});
|
|
1153
|
+
test(formatTitle(_name, require_helpers.toArray(item), idx), options, handlerWrapper);
|
|
1154
|
+
});
|
|
1155
|
+
};
|
|
1156
|
+
};
|
|
1157
|
+
taskFn.skipIf = function(condition) {
|
|
1158
|
+
return condition ? this.skip : this;
|
|
1159
|
+
};
|
|
1160
|
+
taskFn.runIf = function(condition) {
|
|
1161
|
+
return condition ? this : this.skip;
|
|
1162
|
+
};
|
|
1163
|
+
/**
|
|
1164
|
+
* Parse builder pattern arguments into a fixtures object.
|
|
1165
|
+
* Handles both builder pattern (name, options?, value) and object syntax.
|
|
1166
|
+
*/
|
|
1167
|
+
function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1168
|
+
if (typeof fixturesOrName !== "string") return fixturesOrName;
|
|
1169
|
+
const fixtureName = fixturesOrName;
|
|
1170
|
+
let fixtureOptions;
|
|
1171
|
+
let fixtureValue;
|
|
1172
|
+
if (maybeFn !== void 0) {
|
|
1173
|
+
fixtureOptions = optionsOrFn;
|
|
1174
|
+
fixtureValue = maybeFn;
|
|
1175
|
+
} else if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && TestFixtures.isFixtureOptions(optionsOrFn)) {
|
|
1176
|
+
fixtureOptions = optionsOrFn;
|
|
1177
|
+
fixtureValue = {};
|
|
1178
|
+
} else {
|
|
1179
|
+
fixtureOptions = void 0;
|
|
1180
|
+
fixtureValue = optionsOrFn;
|
|
1181
|
+
}
|
|
1182
|
+
if (typeof fixtureValue === "function") {
|
|
1183
|
+
const builderFn = fixtureValue;
|
|
1184
|
+
const fixture = async (ctx, use) => {
|
|
1185
|
+
let cleanup;
|
|
1186
|
+
const onCleanup = (fn) => {
|
|
1187
|
+
if (cleanup !== void 0) throw new Error("onCleanup can only be called once per fixture. Define separate fixtures if you need multiple cleanup functions.");
|
|
1188
|
+
cleanup = fn;
|
|
1189
|
+
};
|
|
1190
|
+
await use(await builderFn(ctx, { onCleanup }));
|
|
1191
|
+
if (cleanup) await cleanup();
|
|
1192
|
+
};
|
|
1193
|
+
configureProps(fixture, { original: builderFn });
|
|
1194
|
+
if (fixtureOptions) return { [fixtureName]: [fixture, fixtureOptions] };
|
|
1195
|
+
return { [fixtureName]: fixture };
|
|
1196
|
+
}
|
|
1197
|
+
if (fixtureOptions) return { [fixtureName]: [fixtureValue, fixtureOptions] };
|
|
1198
|
+
return { [fixtureName]: fixtureValue };
|
|
1199
|
+
}
|
|
1200
|
+
taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1201
|
+
const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
|
|
1202
|
+
getChainableContext(this).getFixtures().override(runner, userFixtures);
|
|
1203
|
+
return this;
|
|
1204
|
+
};
|
|
1205
|
+
taskFn.scoped = function(fixtures) {
|
|
1206
|
+
console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
|
|
1207
|
+
return this.override(fixtures);
|
|
1208
|
+
};
|
|
1209
|
+
taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1210
|
+
const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
|
|
1211
|
+
const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
|
|
1212
|
+
const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
|
|
1213
|
+
fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
|
|
1214
|
+
});
|
|
1215
|
+
getChainableContext(_test).mergeContext({ fixtures });
|
|
1216
|
+
return _test;
|
|
1217
|
+
};
|
|
1218
|
+
taskFn.describe = suite;
|
|
1219
|
+
taskFn.suite = suite;
|
|
1220
|
+
taskFn.beforeEach = beforeEach;
|
|
1221
|
+
taskFn.afterEach = afterEach;
|
|
1222
|
+
taskFn.beforeAll = beforeAll;
|
|
1223
|
+
taskFn.afterAll = afterAll;
|
|
1224
|
+
taskFn.aroundEach = aroundEach;
|
|
1225
|
+
taskFn.aroundAll = aroundAll;
|
|
1226
|
+
return createChainable([
|
|
1227
|
+
"concurrent",
|
|
1228
|
+
"sequential",
|
|
1229
|
+
"skip",
|
|
1230
|
+
"only",
|
|
1231
|
+
"todo",
|
|
1232
|
+
"fails"
|
|
1233
|
+
], taskFn, { fixtures: new TestFixtures() });
|
|
1234
|
+
}
|
|
1235
|
+
function createTest(fn) {
|
|
1236
|
+
return createTaskCollector(fn);
|
|
1237
|
+
}
|
|
1238
|
+
function formatName(name) {
|
|
1239
|
+
return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
|
|
1240
|
+
}
|
|
1241
|
+
function formatTitle(template, items, idx) {
|
|
1242
|
+
if (template.includes("%#") || template.includes("%$")) template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/%\$/g, `${idx + 1}`).replace(/__vitest_escaped_%__/g, "%%");
|
|
1243
|
+
const count = template.split("%").length - 1;
|
|
1244
|
+
if (template.includes("%f")) (template.match(/%f/g) || []).forEach((_, i) => {
|
|
1245
|
+
if (require_helpers.isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
|
|
1246
|
+
let occurrence = 0;
|
|
1247
|
+
template = template.replace(/%f/g, (match) => {
|
|
1248
|
+
occurrence++;
|
|
1249
|
+
return occurrence === i + 1 ? "-%f" : match;
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
const isObjectItem = require_helpers.isObject(items[0]);
|
|
1254
|
+
function formatAttribute(s) {
|
|
1255
|
+
return s.replace(/\$([$\w.]+)/g, (_, key) => {
|
|
1256
|
+
const isArrayKey = /^\d+$/.test(key);
|
|
1257
|
+
if (!isObjectItem && !isArrayKey) return `$${key}`;
|
|
1258
|
+
const arrayElement = isArrayKey ? require_helpers.objectAttr(items, key) : void 0;
|
|
1259
|
+
return require_display.objDisplay(isObjectItem ? require_helpers.objectAttr(items[0], key, arrayElement) : arrayElement, { truncate: runner?.config?.chaiConfig?.truncateThreshold });
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
let output = "";
|
|
1263
|
+
let i = 0;
|
|
1264
|
+
handleRegexMatch(template, require_display.formatRegExp, (match) => {
|
|
1265
|
+
if (i < count) output += require_display.format(match[0], items[i++]);
|
|
1266
|
+
else output += match[0];
|
|
1267
|
+
}, (nonMatch) => {
|
|
1268
|
+
output += formatAttribute(nonMatch);
|
|
1269
|
+
});
|
|
1270
|
+
return output;
|
|
1271
|
+
}
|
|
1272
|
+
function handleRegexMatch(input, regex, onMatch, onNonMatch) {
|
|
1273
|
+
let lastIndex = 0;
|
|
1274
|
+
for (const m of input.matchAll(regex)) {
|
|
1275
|
+
if (lastIndex < m.index) onNonMatch(input.slice(lastIndex, m.index));
|
|
1276
|
+
onMatch(m);
|
|
1277
|
+
lastIndex = m.index + m[0].length;
|
|
1278
|
+
}
|
|
1279
|
+
if (lastIndex < input.length) onNonMatch(input.slice(lastIndex));
|
|
1280
|
+
}
|
|
1281
|
+
function formatTemplateString(cases, args) {
|
|
1282
|
+
const header = cases.join("").trim().replace(/ /g, "").split("\n").map((i) => i.split("|"))[0];
|
|
1283
|
+
const res = [];
|
|
1284
|
+
for (let i = 0; i < Math.floor(args.length / header.length); i++) {
|
|
1285
|
+
const oneCase = {};
|
|
1286
|
+
for (let j = 0; j < header.length; j++) oneCase[header[j]] = args[i * header.length + j];
|
|
1287
|
+
res.push(oneCase);
|
|
1288
|
+
}
|
|
1289
|
+
return res;
|
|
1290
|
+
}
|
|
1291
|
+
const now$2 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
1292
|
+
const collectorContext = {
|
|
1293
|
+
tasks: [],
|
|
1294
|
+
currentSuite: null
|
|
1295
|
+
};
|
|
1296
|
+
function collectTask(task) {
|
|
1297
|
+
collectorContext.currentSuite?.tasks.push(task);
|
|
1298
|
+
}
|
|
1299
|
+
async function runWithSuite(suite, fn) {
|
|
1300
|
+
const prev = collectorContext.currentSuite;
|
|
1301
|
+
collectorContext.currentSuite = suite;
|
|
1302
|
+
await fn();
|
|
1303
|
+
collectorContext.currentSuite = prev;
|
|
1304
|
+
}
|
|
1305
|
+
function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
|
|
1306
|
+
if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) return fn;
|
|
1307
|
+
const { setTimeout, clearTimeout } = require_timers.getSafeTimers();
|
|
1308
|
+
return function runWithTimeout(...args) {
|
|
1309
|
+
const startTime = now$2();
|
|
1310
|
+
const runner = getRunner();
|
|
1311
|
+
runner._currentTaskStartTime = startTime;
|
|
1312
|
+
runner._currentTaskTimeout = timeout;
|
|
1313
|
+
return new Promise((resolve_, reject_) => {
|
|
1314
|
+
const timer = setTimeout(() => {
|
|
1315
|
+
clearTimeout(timer);
|
|
1316
|
+
rejectTimeoutError();
|
|
1317
|
+
}, timeout);
|
|
1318
|
+
timer.unref?.();
|
|
1319
|
+
function rejectTimeoutError() {
|
|
1320
|
+
const error = makeTimeoutError(isHook, timeout, stackTraceError);
|
|
1321
|
+
onTimeout?.(args, error);
|
|
1322
|
+
reject_(error);
|
|
1323
|
+
}
|
|
1324
|
+
function resolve(result) {
|
|
1325
|
+
runner._currentTaskStartTime = void 0;
|
|
1326
|
+
runner._currentTaskTimeout = void 0;
|
|
1327
|
+
clearTimeout(timer);
|
|
1328
|
+
if (now$2() - startTime >= timeout) {
|
|
1329
|
+
rejectTimeoutError();
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
resolve_(result);
|
|
1333
|
+
}
|
|
1334
|
+
function reject(error) {
|
|
1335
|
+
runner._currentTaskStartTime = void 0;
|
|
1336
|
+
runner._currentTaskTimeout = void 0;
|
|
1337
|
+
clearTimeout(timer);
|
|
1338
|
+
reject_(error);
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
const result = fn(...args);
|
|
1342
|
+
if (typeof result === "object" && result != null && typeof result.then === "function") result.then(resolve, reject);
|
|
1343
|
+
else resolve(result);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
reject(error);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
function withCancel(fn, signal) {
|
|
1351
|
+
return function runWithCancel(...args) {
|
|
1352
|
+
return new Promise((resolve, reject) => {
|
|
1353
|
+
signal.addEventListener("abort", () => reject(signal.reason));
|
|
1354
|
+
try {
|
|
1355
|
+
const result = fn(...args);
|
|
1356
|
+
if (typeof result === "object" && result != null && typeof result.then === "function") result.then(resolve, reject);
|
|
1357
|
+
else resolve(result);
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
reject(error);
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
const abortControllers = /* @__PURE__ */ new WeakMap();
|
|
1365
|
+
function abortIfTimeout([context], error) {
|
|
1366
|
+
if (context) abortContextSignal(context, error);
|
|
1367
|
+
}
|
|
1368
|
+
function abortContextSignal(context, error) {
|
|
1369
|
+
abortControllers.get(context)?.abort(error);
|
|
1370
|
+
}
|
|
1371
|
+
function createTestContext(test, runner) {
|
|
1372
|
+
const context = function() {
|
|
1373
|
+
throw new Error("done() callback is deprecated, use promise instead");
|
|
1374
|
+
};
|
|
1375
|
+
let abortController = abortControllers.get(context);
|
|
1376
|
+
if (!abortController) {
|
|
1377
|
+
abortController = new AbortController();
|
|
1378
|
+
abortControllers.set(context, abortController);
|
|
1379
|
+
}
|
|
1380
|
+
context.signal = abortController.signal;
|
|
1381
|
+
context.task = test;
|
|
1382
|
+
context.skip = (condition, note) => {
|
|
1383
|
+
if (condition === false) return;
|
|
1384
|
+
test.result ??= { state: "skip" };
|
|
1385
|
+
test.result.pending = true;
|
|
1386
|
+
throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
|
|
1387
|
+
};
|
|
1388
|
+
context.annotate = (message, type, attachment) => {
|
|
1389
|
+
if (test.result && test.result.state !== "run") throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`);
|
|
1390
|
+
const annotation = {
|
|
1391
|
+
message,
|
|
1392
|
+
type: typeof type === "object" || type === void 0 ? "notice" : type
|
|
1393
|
+
};
|
|
1394
|
+
const annotationAttachment = typeof type === "object" ? type : attachment;
|
|
1395
|
+
if (annotationAttachment) {
|
|
1396
|
+
annotation.attachment = annotationAttachment;
|
|
1397
|
+
manageArtifactAttachment(annotation.attachment);
|
|
1398
|
+
}
|
|
1399
|
+
return recordAsyncOperation(test, recordArtifact(test, {
|
|
1400
|
+
type: "internal:annotation",
|
|
1401
|
+
annotation
|
|
1402
|
+
}).then(async ({ annotation }) => {
|
|
1403
|
+
if (!runner.onTestAnnotate) throw new Error(`Test runner doesn't support test annotations.`);
|
|
1404
|
+
await finishSendTasksUpdate(runner);
|
|
1405
|
+
const resolvedAnnotation = await runner.onTestAnnotate(test, annotation);
|
|
1406
|
+
test.annotations.push(resolvedAnnotation);
|
|
1407
|
+
return resolvedAnnotation;
|
|
1408
|
+
}));
|
|
1409
|
+
};
|
|
1410
|
+
context.onTestFailed = (handler, timeout) => {
|
|
1411
|
+
test.onFailed ||= [];
|
|
1412
|
+
test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
|
|
1413
|
+
};
|
|
1414
|
+
context.onTestFinished = (handler, timeout) => {
|
|
1415
|
+
test.onFinished ||= [];
|
|
1416
|
+
test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, /* @__PURE__ */ new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
|
|
1417
|
+
};
|
|
1418
|
+
return runner.extendTaskContext?.(context) || context;
|
|
1419
|
+
}
|
|
1420
|
+
function makeTimeoutError(isHook, timeout, stackTraceError) {
|
|
1421
|
+
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"}".`;
|
|
1422
|
+
const error = new Error(message);
|
|
1423
|
+
if (stackTraceError?.stack) error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
|
|
1424
|
+
return error;
|
|
1425
|
+
}
|
|
1426
|
+
const now$1 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
1427
|
+
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
1428
|
+
const unixNow = Date.now;
|
|
1429
|
+
const { clearTimeout, setTimeout } = require_timers.getSafeTimers();
|
|
1430
|
+
const packs = /* @__PURE__ */ new Map();
|
|
1431
|
+
const eventsPacks = [];
|
|
1432
|
+
const pendingTasksUpdates = [];
|
|
1433
|
+
function sendTasksUpdate(runner) {
|
|
1434
|
+
if (packs.size) {
|
|
1435
|
+
const taskPacks = Array.from(packs).map(([id, task]) => {
|
|
1436
|
+
return [
|
|
1437
|
+
id,
|
|
1438
|
+
task[0],
|
|
1439
|
+
task[1]
|
|
1440
|
+
];
|
|
1441
|
+
});
|
|
1442
|
+
const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
|
|
1443
|
+
if (p) {
|
|
1444
|
+
pendingTasksUpdates.push(p);
|
|
1445
|
+
p.then(() => pendingTasksUpdates.splice(pendingTasksUpdates.indexOf(p), 1), () => {});
|
|
1446
|
+
}
|
|
1447
|
+
eventsPacks.length = 0;
|
|
1448
|
+
packs.clear();
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
async function finishSendTasksUpdate(runner) {
|
|
1452
|
+
sendTasksUpdate(runner);
|
|
1453
|
+
await Promise.all(pendingTasksUpdates);
|
|
1454
|
+
}
|
|
1455
|
+
function throttle(fn, ms) {
|
|
1456
|
+
let last = 0;
|
|
1457
|
+
let pendingCall;
|
|
1458
|
+
return function call(...args) {
|
|
1459
|
+
const now = unixNow();
|
|
1460
|
+
if (now - last > ms) {
|
|
1461
|
+
last = now;
|
|
1462
|
+
clearTimeout(pendingCall);
|
|
1463
|
+
pendingCall = void 0;
|
|
1464
|
+
return fn.apply(this, args);
|
|
1465
|
+
}
|
|
1466
|
+
pendingCall ??= setTimeout(() => call.bind(this)(...args), ms);
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
const sendTasksUpdateThrottled = throttle(sendTasksUpdate, 100);
|
|
1470
|
+
/**
|
|
1471
|
+
* @experimental
|
|
1472
|
+
* @advanced
|
|
1473
|
+
*
|
|
1474
|
+
* Records a custom test artifact during test execution.
|
|
1475
|
+
*
|
|
1476
|
+
* This function allows you to attach structured data, files, or metadata to a test.
|
|
1477
|
+
*
|
|
1478
|
+
* Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
|
|
1479
|
+
*
|
|
1480
|
+
* **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
|
|
1481
|
+
*
|
|
1482
|
+
* @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
|
|
1483
|
+
* @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
|
|
1484
|
+
*
|
|
1485
|
+
* @returns A promise that resolves to the recorded artifact with location injected
|
|
1486
|
+
*
|
|
1487
|
+
* @throws {Error} If the test runner doesn't support artifacts
|
|
1488
|
+
*
|
|
1489
|
+
* @example
|
|
1490
|
+
* ```ts
|
|
1491
|
+
* // In a custom assertion
|
|
1492
|
+
* async function toHaveValidSchema(this: MatcherState, actual: unknown) {
|
|
1493
|
+
* const validation = validateSchema(actual)
|
|
1494
|
+
*
|
|
1495
|
+
* await recordArtifact(this.task, {
|
|
1496
|
+
* type: 'my-plugin:schema-validation',
|
|
1497
|
+
* passed: validation.valid,
|
|
1498
|
+
* errors: validation.errors,
|
|
1499
|
+
* })
|
|
1500
|
+
*
|
|
1501
|
+
* return { pass: validation.valid, message: () => '...' }
|
|
1502
|
+
* }
|
|
1503
|
+
* ```
|
|
1504
|
+
*/
|
|
1505
|
+
async function recordArtifact(task, artifact) {
|
|
1506
|
+
const runner = getRunner();
|
|
1507
|
+
const stack = findTestFileStackTrace(task.file.filepath, (/* @__PURE__ */ new Error("STACK_TRACE")).stack);
|
|
1508
|
+
if (stack) {
|
|
1509
|
+
artifact.location = {
|
|
1510
|
+
file: stack.file,
|
|
1511
|
+
line: stack.line,
|
|
1512
|
+
column: stack.column
|
|
1513
|
+
};
|
|
1514
|
+
if (artifact.type === "internal:annotation") artifact.annotation.location = artifact.location;
|
|
1515
|
+
}
|
|
1516
|
+
if (Array.isArray(artifact.attachments)) for (const attachment of artifact.attachments) manageArtifactAttachment(attachment);
|
|
1517
|
+
if (artifact.type === "internal:annotation") return artifact;
|
|
1518
|
+
if (!runner.onTestArtifactRecord) throw new Error(`Test runner doesn't support test artifacts.`);
|
|
1519
|
+
await finishSendTasksUpdate(runner);
|
|
1520
|
+
const resolvedArtifact = await runner.onTestArtifactRecord(task, artifact);
|
|
1521
|
+
task.artifacts.push(resolvedArtifact);
|
|
1522
|
+
return resolvedArtifact;
|
|
1523
|
+
}
|
|
1524
|
+
const table = [];
|
|
1525
|
+
for (let i = 65; i < 91; i++) table.push(String.fromCharCode(i));
|
|
1526
|
+
for (let i = 97; i < 123; i++) table.push(String.fromCharCode(i));
|
|
1527
|
+
for (let i = 0; i < 10; i++) table.push(i.toString(10));
|
|
1528
|
+
table.push("+", "/");
|
|
1529
|
+
function encodeUint8Array(bytes) {
|
|
1530
|
+
let base64 = "";
|
|
1531
|
+
const len = bytes.byteLength;
|
|
1532
|
+
for (let i = 0; i < len; i += 3) if (len === i + 1) {
|
|
1533
|
+
const a = (bytes[i] & 252) >> 2;
|
|
1534
|
+
const b = (bytes[i] & 3) << 4;
|
|
1535
|
+
base64 += table[a];
|
|
1536
|
+
base64 += table[b];
|
|
1537
|
+
base64 += "==";
|
|
1538
|
+
} else if (len === i + 2) {
|
|
1539
|
+
const a = (bytes[i] & 252) >> 2;
|
|
1540
|
+
const b = (bytes[i] & 3) << 4 | (bytes[i + 1] & 240) >> 4;
|
|
1541
|
+
const c = (bytes[i + 1] & 15) << 2;
|
|
1542
|
+
base64 += table[a];
|
|
1543
|
+
base64 += table[b];
|
|
1544
|
+
base64 += table[c];
|
|
1545
|
+
base64 += "=";
|
|
1546
|
+
} else {
|
|
1547
|
+
const a = (bytes[i] & 252) >> 2;
|
|
1548
|
+
const b = (bytes[i] & 3) << 4 | (bytes[i + 1] & 240) >> 4;
|
|
1549
|
+
const c = (bytes[i + 1] & 15) << 2 | (bytes[i + 2] & 192) >> 6;
|
|
1550
|
+
const d = bytes[i + 2] & 63;
|
|
1551
|
+
base64 += table[a];
|
|
1552
|
+
base64 += table[b];
|
|
1553
|
+
base64 += table[c];
|
|
1554
|
+
base64 += table[d];
|
|
1555
|
+
}
|
|
1556
|
+
return base64;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Records an async operation associated with a test task.
|
|
1560
|
+
*
|
|
1561
|
+
* This function tracks promises that should be awaited before a test completes.
|
|
1562
|
+
* The promise is automatically removed from the test's promise list once it settles.
|
|
1563
|
+
*/
|
|
1564
|
+
function recordAsyncOperation(test, promise) {
|
|
1565
|
+
promise = promise.finally(() => {
|
|
1566
|
+
if (!test.promises) return;
|
|
1567
|
+
const index = test.promises.indexOf(promise);
|
|
1568
|
+
if (index !== -1) test.promises.splice(index, 1);
|
|
1569
|
+
});
|
|
1570
|
+
if (!test.promises) test.promises = [];
|
|
1571
|
+
test.promises.push(promise);
|
|
1572
|
+
return promise;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Validates and prepares a test attachment for serialization.
|
|
1576
|
+
*
|
|
1577
|
+
* This function ensures attachments have either `body` or `path` set (but not both), and converts `Uint8Array` bodies to base64-encoded strings for easier serialization.
|
|
1578
|
+
*
|
|
1579
|
+
* @param attachment - The attachment to validate and prepare
|
|
1580
|
+
*
|
|
1581
|
+
* @throws {TypeError} If neither `body` nor `path` is provided
|
|
1582
|
+
* @throws {TypeError} If both `body` and `path` are provided
|
|
1583
|
+
*/
|
|
1584
|
+
function manageArtifactAttachment(attachment) {
|
|
1585
|
+
if (attachment.body == null && !attachment.path) throw new TypeError(`Test attachment requires "body" or "path" to be set. Both are missing.`);
|
|
1586
|
+
if (attachment.body && attachment.path) throw new TypeError(`Test attachment requires only one of "body" or "path" to be set. Both are specified.`);
|
|
1587
|
+
if (attachment.path && attachment.bodyEncoding) throw new TypeError(`Test attachment with "path" should not have "bodyEncoding" specified.`);
|
|
1588
|
+
if (attachment.body instanceof Uint8Array) attachment.body = encodeUint8Array(attachment.body);
|
|
1589
|
+
if (attachment.body != null) attachment.bodyEncoding ??= "base64";
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
//#endregion
|
|
1593
|
+
exports.afterAll = afterAll;
|
|
1594
|
+
exports.afterEach = afterEach;
|
|
1595
|
+
exports.aroundAll = aroundAll;
|
|
1596
|
+
exports.aroundEach = aroundEach;
|
|
1597
|
+
exports.beforeAll = beforeAll;
|
|
1598
|
+
exports.beforeEach = beforeEach;
|
|
1599
|
+
exports.createChainable = createChainable;
|
|
1600
|
+
exports.createTaskCollector = createTaskCollector;
|
|
1601
|
+
exports.createTaskName = createTaskName;
|
|
1602
|
+
exports.describe = describe;
|
|
1603
|
+
exports.findTestFileStackTrace = findTestFileStackTrace;
|
|
1604
|
+
exports.getCurrentSuite = getCurrentSuite;
|
|
1605
|
+
exports.getCurrentTest = getCurrentTest;
|
|
1606
|
+
exports.getHooks = getHooks;
|
|
1607
|
+
exports.getNames = getNames;
|
|
1608
|
+
exports.it = it;
|
|
1609
|
+
exports.recordArtifact = recordArtifact;
|
|
1610
|
+
exports.setFn = setFn;
|
|
1611
|
+
exports.setHooks = setHooks;
|
|
1612
|
+
exports.suite = suite;
|
|
1613
|
+
exports.test = test;
|
|
1614
|
+
exports.validateTags = validateTags;
|