@vitest/runner 3.0.9 → 3.1.0-beta.2
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/chunk-tasks.js +245 -243
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1398 -1285
- package/dist/{tasks.d-D4e98wjH.d.ts → tasks.d-CnuPOyeL.d.ts} +7 -1
- package/dist/types.d.ts +2 -2
- package/dist/utils.d.ts +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { getSafeTimers, isObject, createDefer, toArray, isNegativeNaN, format,
|
1
|
+
import { getSafeTimers, isObject, createDefer, toArray, isNegativeNaN, format, objectAttr, objDisplay, assertTypes, shuffle } from '@vitest/utils';
|
2
2
|
import { parseSingleStack } from '@vitest/utils/source-map';
|
3
3
|
import { c as createChainable, b as createFileTask, a as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, l as limitConcurrency, p as partitionSuiteChildren, o as hasTests, n as hasFailed } from './chunk-tasks.js';
|
4
4
|
import { processError } from '@vitest/utils/error';
|
@@ -6,1432 +6,1545 @@ export { processError } from '@vitest/utils/error';
|
|
6
6
|
import 'pathe';
|
7
7
|
|
8
8
|
class PendingError extends Error {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
code = "VITEST_PENDING";
|
10
|
+
taskId;
|
11
|
+
constructor(message, task, note) {
|
12
|
+
super(message);
|
13
|
+
this.message = message;
|
14
|
+
this.note = note;
|
15
|
+
this.taskId = task.id;
|
16
|
+
}
|
17
17
|
}
|
18
18
|
|
19
19
|
const now$2 = Date.now;
|
20
20
|
const collectorContext = {
|
21
|
-
|
22
|
-
|
21
|
+
tasks: [],
|
22
|
+
currentSuite: null
|
23
23
|
};
|
24
24
|
function collectTask(task) {
|
25
|
-
|
26
|
-
|
25
|
+
var _collectorContext$cur;
|
26
|
+
(_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
|
27
27
|
}
|
28
28
|
async function runWithSuite(suite, fn) {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
const prev = collectorContext.currentSuite;
|
30
|
+
collectorContext.currentSuite = suite;
|
31
|
+
await fn();
|
32
|
+
collectorContext.currentSuite = prev;
|
33
33
|
}
|
34
34
|
function withTimeout(fn, timeout, isHook = false, stackTraceError) {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
35
|
+
if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) {
|
36
|
+
return fn;
|
37
|
+
}
|
38
|
+
const { setTimeout, clearTimeout } = getSafeTimers();
|
39
|
+
return function runWithTimeout(...args) {
|
40
|
+
const startTime = now$2();
|
41
|
+
return new Promise((resolve_, reject_) => {
|
42
|
+
var _timer$unref;
|
43
|
+
const timer = setTimeout(() => {
|
44
|
+
clearTimeout(timer);
|
45
|
+
rejectTimeoutError();
|
46
|
+
}, timeout);
|
47
|
+
(_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
|
48
|
+
function rejectTimeoutError() {
|
49
|
+
reject_(makeTimeoutError(isHook, timeout, stackTraceError));
|
50
|
+
}
|
51
|
+
function resolve(result) {
|
52
|
+
clearTimeout(timer);
|
53
|
+
if (now$2() - startTime >= timeout) {
|
54
|
+
rejectTimeoutError();
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
resolve_(result);
|
58
|
+
}
|
59
|
+
function reject(error) {
|
60
|
+
clearTimeout(timer);
|
61
|
+
reject_(error);
|
62
|
+
}
|
63
|
+
try {
|
64
|
+
const result = fn(...args);
|
65
|
+
if (typeof result === "object" && result != null && typeof result.then === "function") {
|
66
|
+
result.then(resolve, reject);
|
67
|
+
} else {
|
68
|
+
resolve(result);
|
69
|
+
}
|
70
|
+
} catch (error) {
|
71
|
+
reject(error);
|
72
|
+
}
|
73
|
+
});
|
74
|
+
};
|
75
75
|
}
|
76
76
|
function createTestContext(test, runner) {
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
return ((_a = runner.extendTaskContext) == null ? void 0 : _a.call(runner, context)) || context;
|
77
|
+
var _runner$extendTaskCon;
|
78
|
+
const context = function() {
|
79
|
+
throw new Error("done() callback is deprecated, use promise instead");
|
80
|
+
};
|
81
|
+
context.task = test;
|
82
|
+
context.skip = (condition, note) => {
|
83
|
+
if (condition === false) {
|
84
|
+
return undefined;
|
85
|
+
}
|
86
|
+
test.result ?? (test.result = { state: "skip" });
|
87
|
+
test.result.pending = true;
|
88
|
+
throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
|
89
|
+
};
|
90
|
+
context.onTestFailed = (handler, timeout) => {
|
91
|
+
test.onFailed || (test.onFailed = []);
|
92
|
+
test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR")));
|
93
|
+
};
|
94
|
+
context.onTestFinished = (handler, timeout) => {
|
95
|
+
test.onFinished || (test.onFinished = []);
|
96
|
+
test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR")));
|
97
|
+
};
|
98
|
+
return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
|
100
99
|
}
|
101
100
|
function makeTimeoutError(isHook, timeout, stackTraceError) {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
return error;
|
101
|
+
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"}".`;
|
102
|
+
const error = new Error(message);
|
103
|
+
if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
|
104
|
+
error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
|
105
|
+
}
|
106
|
+
return error;
|
109
107
|
}
|
110
108
|
|
111
|
-
const fnMap =
|
112
|
-
const
|
113
|
-
const hooksMap =
|
109
|
+
const fnMap = new WeakMap();
|
110
|
+
const testFixtureMap = new WeakMap();
|
111
|
+
const hooksMap = new WeakMap();
|
114
112
|
function setFn(key, fn) {
|
115
|
-
|
113
|
+
fnMap.set(key, fn);
|
116
114
|
}
|
117
115
|
function getFn(key) {
|
118
|
-
|
116
|
+
return fnMap.get(key);
|
119
117
|
}
|
120
|
-
function
|
121
|
-
|
118
|
+
function setTestFixture(key, fixture) {
|
119
|
+
testFixtureMap.set(key, fixture);
|
122
120
|
}
|
123
|
-
function
|
124
|
-
|
121
|
+
function getTestFixture(key) {
|
122
|
+
return testFixtureMap.get(key);
|
125
123
|
}
|
126
124
|
function setHooks(key, hooks) {
|
127
|
-
|
125
|
+
hooksMap.set(key, hooks);
|
128
126
|
}
|
129
127
|
function getHooks(key) {
|
130
|
-
|
128
|
+
return hooksMap.get(key);
|
131
129
|
}
|
132
130
|
|
131
|
+
function mergeScopedFixtures(testFixtures, scopedFixtures) {
|
132
|
+
const scopedFixturesMap = scopedFixtures.reduce((map, fixture) => {
|
133
|
+
map[fixture.prop] = fixture;
|
134
|
+
return map;
|
135
|
+
}, {});
|
136
|
+
const newFixtures = {};
|
137
|
+
testFixtures.forEach((fixture) => {
|
138
|
+
const useFixture = scopedFixturesMap[fixture.prop] || { ...fixture };
|
139
|
+
newFixtures[useFixture.prop] = useFixture;
|
140
|
+
});
|
141
|
+
for (const fixtureKep in newFixtures) {
|
142
|
+
var _fixture$deps;
|
143
|
+
const fixture = newFixtures[fixtureKep];
|
144
|
+
fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep) => newFixtures[dep.prop]);
|
145
|
+
}
|
146
|
+
return Object.values(newFixtures);
|
147
|
+
}
|
133
148
|
function mergeContextFixtures(fixtures, context, inject) {
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
return context;
|
164
|
-
}
|
165
|
-
const fixtureValueMaps = /* @__PURE__ */ new Map();
|
166
|
-
const cleanupFnArrayMap = /* @__PURE__ */ new Map();
|
149
|
+
const fixtureOptionKeys = ["auto", "injected"];
|
150
|
+
const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
|
151
|
+
const fixtureItem = { value };
|
152
|
+
if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
|
153
|
+
Object.assign(fixtureItem, value[1]);
|
154
|
+
const userValue = value[0];
|
155
|
+
fixtureItem.value = fixtureItem.injected ? inject(prop) ?? userValue : userValue;
|
156
|
+
}
|
157
|
+
fixtureItem.prop = prop;
|
158
|
+
fixtureItem.isFn = typeof fixtureItem.value === "function";
|
159
|
+
return fixtureItem;
|
160
|
+
});
|
161
|
+
if (Array.isArray(context.fixtures)) {
|
162
|
+
context.fixtures = context.fixtures.concat(fixtureArray);
|
163
|
+
} else {
|
164
|
+
context.fixtures = fixtureArray;
|
165
|
+
}
|
166
|
+
fixtureArray.forEach((fixture) => {
|
167
|
+
if (fixture.isFn) {
|
168
|
+
const usedProps = getUsedProps(fixture.value);
|
169
|
+
if (usedProps.length) {
|
170
|
+
fixture.deps = context.fixtures.filter(({ prop }) => prop !== fixture.prop && usedProps.includes(prop));
|
171
|
+
}
|
172
|
+
}
|
173
|
+
});
|
174
|
+
return context;
|
175
|
+
}
|
176
|
+
const fixtureValueMaps = new Map();
|
177
|
+
const cleanupFnArrayMap = new Map();
|
167
178
|
async function callFixtureCleanup(context) {
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
179
|
+
const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
|
180
|
+
for (const cleanup of cleanupFnArray.reverse()) {
|
181
|
+
await cleanup();
|
182
|
+
}
|
183
|
+
cleanupFnArrayMap.delete(context);
|
173
184
|
}
|
174
185
|
function withFixtures(fn, testContext) {
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
return resolveFixtures().then(() => fn(context));
|
218
|
-
};
|
186
|
+
return (hookContext) => {
|
187
|
+
const context = hookContext || testContext;
|
188
|
+
if (!context) {
|
189
|
+
return fn({});
|
190
|
+
}
|
191
|
+
const fixtures = getTestFixture(context);
|
192
|
+
if (!(fixtures === null || fixtures === void 0 ? void 0 : fixtures.length)) {
|
193
|
+
return fn(context);
|
194
|
+
}
|
195
|
+
const usedProps = getUsedProps(fn);
|
196
|
+
const hasAutoFixture = fixtures.some(({ auto }) => auto);
|
197
|
+
if (!usedProps.length && !hasAutoFixture) {
|
198
|
+
return fn(context);
|
199
|
+
}
|
200
|
+
if (!fixtureValueMaps.get(context)) {
|
201
|
+
fixtureValueMaps.set(context, new Map());
|
202
|
+
}
|
203
|
+
const fixtureValueMap = fixtureValueMaps.get(context);
|
204
|
+
if (!cleanupFnArrayMap.has(context)) {
|
205
|
+
cleanupFnArrayMap.set(context, []);
|
206
|
+
}
|
207
|
+
const cleanupFnArray = cleanupFnArrayMap.get(context);
|
208
|
+
const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop));
|
209
|
+
const pendingFixtures = resolveDeps(usedFixtures);
|
210
|
+
if (!pendingFixtures.length) {
|
211
|
+
return fn(context);
|
212
|
+
}
|
213
|
+
async function resolveFixtures() {
|
214
|
+
for (const fixture of pendingFixtures) {
|
215
|
+
if (fixtureValueMap.has(fixture)) {
|
216
|
+
continue;
|
217
|
+
}
|
218
|
+
const resolvedValue = fixture.isFn ? await resolveFixtureFunction(fixture.value, context, cleanupFnArray) : fixture.value;
|
219
|
+
context[fixture.prop] = resolvedValue;
|
220
|
+
fixtureValueMap.set(fixture, resolvedValue);
|
221
|
+
cleanupFnArray.unshift(() => {
|
222
|
+
fixtureValueMap.delete(fixture);
|
223
|
+
});
|
224
|
+
}
|
225
|
+
}
|
226
|
+
return resolveFixtures().then(() => fn(context));
|
227
|
+
};
|
219
228
|
}
|
220
229
|
async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
}
|
241
|
-
function resolveDeps(fixtures, depSet =
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
});
|
260
|
-
return pendingFixtures;
|
230
|
+
const useFnArgPromise = createDefer();
|
231
|
+
let isUseFnArgResolved = false;
|
232
|
+
const fixtureReturn = fixtureFn(context, async (useFnArg) => {
|
233
|
+
isUseFnArgResolved = true;
|
234
|
+
useFnArgPromise.resolve(useFnArg);
|
235
|
+
const useReturnPromise = createDefer();
|
236
|
+
cleanupFnArray.push(async () => {
|
237
|
+
useReturnPromise.resolve();
|
238
|
+
await fixtureReturn;
|
239
|
+
});
|
240
|
+
await useReturnPromise;
|
241
|
+
}).catch((e) => {
|
242
|
+
if (!isUseFnArgResolved) {
|
243
|
+
useFnArgPromise.reject(e);
|
244
|
+
return;
|
245
|
+
}
|
246
|
+
throw e;
|
247
|
+
});
|
248
|
+
return useFnArgPromise;
|
249
|
+
}
|
250
|
+
function resolveDeps(fixtures, depSet = new Set(), pendingFixtures = []) {
|
251
|
+
fixtures.forEach((fixture) => {
|
252
|
+
if (pendingFixtures.includes(fixture)) {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
if (!fixture.isFn || !fixture.deps) {
|
256
|
+
pendingFixtures.push(fixture);
|
257
|
+
return;
|
258
|
+
}
|
259
|
+
if (depSet.has(fixture)) {
|
260
|
+
throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`);
|
261
|
+
}
|
262
|
+
depSet.add(fixture);
|
263
|
+
resolveDeps(fixture.deps, depSet, pendingFixtures);
|
264
|
+
pendingFixtures.push(fixture);
|
265
|
+
depSet.clear();
|
266
|
+
});
|
267
|
+
return pendingFixtures;
|
261
268
|
}
|
262
269
|
function getUsedProps(fn) {
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
`Rest parameters are not supported in fixtures, received "${last}".`
|
295
|
-
);
|
296
|
-
}
|
297
|
-
return props;
|
270
|
+
let fnString = fn.toString();
|
271
|
+
if (/__async\(this, (?:null|arguments|\[[_0-9, ]*\]), function\*/.test(fnString)) {
|
272
|
+
fnString = fnString.split("__async(this,")[1];
|
273
|
+
}
|
274
|
+
const match = fnString.match(/[^(]*\(([^)]*)/);
|
275
|
+
if (!match) {
|
276
|
+
return [];
|
277
|
+
}
|
278
|
+
const args = splitByComma(match[1]);
|
279
|
+
if (!args.length) {
|
280
|
+
return [];
|
281
|
+
}
|
282
|
+
let first = args[0];
|
283
|
+
if ("__VITEST_FIXTURE_INDEX__" in fn) {
|
284
|
+
first = args[fn.__VITEST_FIXTURE_INDEX__];
|
285
|
+
if (!first) {
|
286
|
+
return [];
|
287
|
+
}
|
288
|
+
}
|
289
|
+
if (!(first.startsWith("{") && first.endsWith("}"))) {
|
290
|
+
throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
|
291
|
+
}
|
292
|
+
const _first = first.slice(1, -1).replace(/\s/g, "");
|
293
|
+
const props = splitByComma(_first).map((prop) => {
|
294
|
+
return prop.replace(/:.*|=.*/g, "");
|
295
|
+
});
|
296
|
+
const last = props.at(-1);
|
297
|
+
if (last && last.startsWith("...")) {
|
298
|
+
throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
|
299
|
+
}
|
300
|
+
return props;
|
298
301
|
}
|
299
302
|
function splitByComma(s) {
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
303
|
+
const result = [];
|
304
|
+
const stack = [];
|
305
|
+
let start = 0;
|
306
|
+
for (let i = 0; i < s.length; i++) {
|
307
|
+
if (s[i] === "{" || s[i] === "[") {
|
308
|
+
stack.push(s[i] === "{" ? "}" : "]");
|
309
|
+
} else if (s[i] === stack[stack.length - 1]) {
|
310
|
+
stack.pop();
|
311
|
+
} else if (!stack.length && s[i] === ",") {
|
312
|
+
const token = s.substring(start, i).trim();
|
313
|
+
if (token) {
|
314
|
+
result.push(token);
|
315
|
+
}
|
316
|
+
start = i + 1;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
const lastToken = s.substring(start).trim();
|
320
|
+
if (lastToken) {
|
321
|
+
result.push(lastToken);
|
322
|
+
}
|
323
|
+
return result;
|
321
324
|
}
|
322
325
|
|
323
326
|
let _test;
|
324
327
|
function setCurrentTest(test) {
|
325
|
-
|
328
|
+
_test = test;
|
326
329
|
}
|
327
330
|
function getCurrentTest() {
|
328
|
-
|
331
|
+
return _test;
|
329
332
|
}
|
330
333
|
|
334
|
+
/**
|
335
|
+
* Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
|
336
|
+
* Suites can contain both tests and other suites, enabling complex test structures.
|
337
|
+
*
|
338
|
+
* @param {string} name - The name of the suite, used for identification and reporting.
|
339
|
+
* @param {Function} fn - A function that defines the tests and suites within this suite.
|
340
|
+
* @example
|
341
|
+
* ```ts
|
342
|
+
* // Define a suite with two tests
|
343
|
+
* suite('Math operations', () => {
|
344
|
+
* test('should add two numbers', () => {
|
345
|
+
* expect(add(1, 2)).toBe(3);
|
346
|
+
* });
|
347
|
+
*
|
348
|
+
* test('should subtract two numbers', () => {
|
349
|
+
* expect(subtract(5, 2)).toBe(3);
|
350
|
+
* });
|
351
|
+
* });
|
352
|
+
* ```
|
353
|
+
* @example
|
354
|
+
* ```ts
|
355
|
+
* // Define nested suites
|
356
|
+
* suite('String operations', () => {
|
357
|
+
* suite('Trimming', () => {
|
358
|
+
* test('should trim whitespace from start and end', () => {
|
359
|
+
* expect(' hello '.trim()).toBe('hello');
|
360
|
+
* });
|
361
|
+
* });
|
362
|
+
*
|
363
|
+
* suite('Concatenation', () => {
|
364
|
+
* test('should concatenate two strings', () => {
|
365
|
+
* expect('hello' + ' ' + 'world').toBe('hello world');
|
366
|
+
* });
|
367
|
+
* });
|
368
|
+
* });
|
369
|
+
* ```
|
370
|
+
*/
|
331
371
|
const suite = createSuite();
|
372
|
+
/**
|
373
|
+
* Defines a test case with a given name and test function. The test function can optionally be configured with test options.
|
374
|
+
*
|
375
|
+
* @param {string | Function} name - The name of the test or a function that will be used as a test name.
|
376
|
+
* @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
|
377
|
+
* @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
|
378
|
+
* @throws {Error} If called inside another test function.
|
379
|
+
* @example
|
380
|
+
* ```ts
|
381
|
+
* // Define a simple test
|
382
|
+
* test('should add two numbers', () => {
|
383
|
+
* expect(add(1, 2)).toBe(3);
|
384
|
+
* });
|
385
|
+
* ```
|
386
|
+
* @example
|
387
|
+
* ```ts
|
388
|
+
* // Define a test with options
|
389
|
+
* test('should subtract two numbers', { retry: 3 }, () => {
|
390
|
+
* expect(subtract(5, 2)).toBe(3);
|
391
|
+
* });
|
392
|
+
* ```
|
393
|
+
*/
|
332
394
|
const test = createTest(function(name, optionsOrFn, optionsOrTest) {
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
}
|
338
|
-
getCurrentSuite().test.fn.call(
|
339
|
-
this,
|
340
|
-
formatName(name),
|
341
|
-
optionsOrFn,
|
342
|
-
optionsOrTest
|
343
|
-
);
|
395
|
+
if (getCurrentTest()) {
|
396
|
+
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.");
|
397
|
+
}
|
398
|
+
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
|
344
399
|
});
|
400
|
+
/**
|
401
|
+
* Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
|
402
|
+
* Suites can contain both tests and other suites, enabling complex test structures.
|
403
|
+
*
|
404
|
+
* @param {string} name - The name of the suite, used for identification and reporting.
|
405
|
+
* @param {Function} fn - A function that defines the tests and suites within this suite.
|
406
|
+
* @example
|
407
|
+
* ```ts
|
408
|
+
* // Define a suite with two tests
|
409
|
+
* describe('Math operations', () => {
|
410
|
+
* test('should add two numbers', () => {
|
411
|
+
* expect(add(1, 2)).toBe(3);
|
412
|
+
* });
|
413
|
+
*
|
414
|
+
* test('should subtract two numbers', () => {
|
415
|
+
* expect(subtract(5, 2)).toBe(3);
|
416
|
+
* });
|
417
|
+
* });
|
418
|
+
* ```
|
419
|
+
* @example
|
420
|
+
* ```ts
|
421
|
+
* // Define nested suites
|
422
|
+
* describe('String operations', () => {
|
423
|
+
* describe('Trimming', () => {
|
424
|
+
* test('should trim whitespace from start and end', () => {
|
425
|
+
* expect(' hello '.trim()).toBe('hello');
|
426
|
+
* });
|
427
|
+
* });
|
428
|
+
*
|
429
|
+
* describe('Concatenation', () => {
|
430
|
+
* test('should concatenate two strings', () => {
|
431
|
+
* expect('hello' + ' ' + 'world').toBe('hello world');
|
432
|
+
* });
|
433
|
+
* });
|
434
|
+
* });
|
435
|
+
* ```
|
436
|
+
*/
|
345
437
|
const describe = suite;
|
438
|
+
/**
|
439
|
+
* Defines a test case with a given name and test function. The test function can optionally be configured with test options.
|
440
|
+
*
|
441
|
+
* @param {string | Function} name - The name of the test or a function that will be used as a test name.
|
442
|
+
* @param {TestOptions | TestFunction} [optionsOrFn] - Optional. The test options or the test function if no explicit name is provided.
|
443
|
+
* @param {number | TestOptions | TestFunction} [optionsOrTest] - Optional. The test function or options, depending on the previous parameters.
|
444
|
+
* @throws {Error} If called inside another test function.
|
445
|
+
* @example
|
446
|
+
* ```ts
|
447
|
+
* // Define a simple test
|
448
|
+
* it('adds two numbers', () => {
|
449
|
+
* expect(add(1, 2)).toBe(3);
|
450
|
+
* });
|
451
|
+
* ```
|
452
|
+
* @example
|
453
|
+
* ```ts
|
454
|
+
* // Define a test with options
|
455
|
+
* it('subtracts two numbers', { retry: 3 }, () => {
|
456
|
+
* expect(subtract(5, 2)).toBe(3);
|
457
|
+
* });
|
458
|
+
* ```
|
459
|
+
*/
|
346
460
|
const it = test;
|
347
461
|
let runner;
|
348
462
|
let defaultSuite;
|
349
463
|
let currentTestFilepath;
|
350
464
|
function assert(condition, message) {
|
351
|
-
|
352
|
-
|
353
|
-
|
465
|
+
if (!condition) {
|
466
|
+
throw new Error(`Vitest failed to find ${message}. This is a bug in Vitest. Please, open an issue with reproduction.`);
|
467
|
+
}
|
354
468
|
}
|
355
469
|
function getDefaultSuite() {
|
356
|
-
|
357
|
-
|
470
|
+
assert(defaultSuite, "the default suite");
|
471
|
+
return defaultSuite;
|
358
472
|
}
|
359
473
|
function getTestFilepath() {
|
360
|
-
|
474
|
+
return currentTestFilepath;
|
361
475
|
}
|
362
476
|
function getRunner() {
|
363
|
-
|
364
|
-
|
477
|
+
assert(runner, "the runner");
|
478
|
+
return runner;
|
365
479
|
}
|
366
|
-
function createDefaultSuite(
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
return collector;
|
480
|
+
function createDefaultSuite(runner) {
|
481
|
+
const config = runner.config.sequence;
|
482
|
+
const collector = suite("", { concurrent: config.concurrent }, () => {});
|
483
|
+
delete collector.suite;
|
484
|
+
return collector;
|
372
485
|
}
|
373
486
|
function clearCollectorContext(filepath, currentRunner) {
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
487
|
+
if (!defaultSuite) {
|
488
|
+
defaultSuite = createDefaultSuite(currentRunner);
|
489
|
+
}
|
490
|
+
runner = currentRunner;
|
491
|
+
currentTestFilepath = filepath;
|
492
|
+
collectorContext.tasks.length = 0;
|
493
|
+
defaultSuite.clear();
|
494
|
+
collectorContext.currentSuite = defaultSuite;
|
382
495
|
}
|
383
496
|
function getCurrentSuite() {
|
384
|
-
|
385
|
-
|
386
|
-
|
497
|
+
const currentSuite = collectorContext.currentSuite || defaultSuite;
|
498
|
+
assert(currentSuite, "the current suite");
|
499
|
+
return currentSuite;
|
387
500
|
}
|
388
501
|
function createSuiteHooks() {
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
502
|
+
return {
|
503
|
+
beforeAll: [],
|
504
|
+
afterAll: [],
|
505
|
+
beforeEach: [],
|
506
|
+
afterEach: []
|
507
|
+
};
|
395
508
|
}
|
396
509
|
function parseArguments(optionsOrFn, optionsOrTest) {
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
return collector;
|
510
|
+
let options = {};
|
511
|
+
let fn = () => {};
|
512
|
+
if (typeof optionsOrTest === "object") {
|
513
|
+
if (typeof optionsOrFn === "object") {
|
514
|
+
throw new TypeError("Cannot use two objects as arguments. Please provide options and a function callback in that order.");
|
515
|
+
}
|
516
|
+
console.warn("Using an object as a third argument is deprecated. Vitest 4 will throw an error if the third argument is not a timeout number. Please use the second argument for options. See more at https://vitest.dev/guide/migration");
|
517
|
+
options = optionsOrTest;
|
518
|
+
} else if (typeof optionsOrTest === "number") {
|
519
|
+
options = { timeout: optionsOrTest };
|
520
|
+
} else if (typeof optionsOrFn === "object") {
|
521
|
+
options = optionsOrFn;
|
522
|
+
}
|
523
|
+
if (typeof optionsOrFn === "function") {
|
524
|
+
if (typeof optionsOrTest === "function") {
|
525
|
+
throw new TypeError("Cannot use two functions as arguments. Please use the second argument for options.");
|
526
|
+
}
|
527
|
+
fn = optionsOrFn;
|
528
|
+
} else if (typeof optionsOrTest === "function") {
|
529
|
+
fn = optionsOrTest;
|
530
|
+
}
|
531
|
+
return {
|
532
|
+
options,
|
533
|
+
handler: fn
|
534
|
+
};
|
535
|
+
}
|
536
|
+
function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
|
537
|
+
const tasks = [];
|
538
|
+
let suite;
|
539
|
+
initSuite(true);
|
540
|
+
const task = function(name = "", options = {}) {
|
541
|
+
var _collectorContext$cur;
|
542
|
+
const timeout = (options === null || options === void 0 ? void 0 : options.timeout) ?? runner.config.testTimeout;
|
543
|
+
const task = {
|
544
|
+
id: "",
|
545
|
+
name,
|
546
|
+
suite: (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.suite,
|
547
|
+
each: options.each,
|
548
|
+
fails: options.fails,
|
549
|
+
context: undefined,
|
550
|
+
type: "test",
|
551
|
+
file: undefined,
|
552
|
+
timeout,
|
553
|
+
retry: options.retry ?? runner.config.retry,
|
554
|
+
repeats: options.repeats,
|
555
|
+
mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
|
556
|
+
meta: options.meta ?? Object.create(null)
|
557
|
+
};
|
558
|
+
const handler = options.handler;
|
559
|
+
if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
|
560
|
+
task.concurrent = true;
|
561
|
+
}
|
562
|
+
task.shuffle = suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle;
|
563
|
+
const context = createTestContext(task, runner);
|
564
|
+
Object.defineProperty(task, "context", {
|
565
|
+
value: context,
|
566
|
+
enumerable: false
|
567
|
+
});
|
568
|
+
setTestFixture(context, options.fixtures);
|
569
|
+
if (handler) {
|
570
|
+
setFn(task, withTimeout(withAwaitAsyncAssertions(withFixtures(handler, context), task), timeout));
|
571
|
+
}
|
572
|
+
if (runner.config.includeTaskLocation) {
|
573
|
+
const limit = Error.stackTraceLimit;
|
574
|
+
Error.stackTraceLimit = 15;
|
575
|
+
const error = new Error("stacktrace").stack;
|
576
|
+
Error.stackTraceLimit = limit;
|
577
|
+
const stack = findTestFileStackTrace(error, task.each ?? false);
|
578
|
+
if (stack) {
|
579
|
+
task.location = stack;
|
580
|
+
}
|
581
|
+
}
|
582
|
+
tasks.push(task);
|
583
|
+
return task;
|
584
|
+
};
|
585
|
+
const test = createTest(function(name, optionsOrFn, optionsOrTest) {
|
586
|
+
let { options, handler } = parseArguments(optionsOrFn, optionsOrTest);
|
587
|
+
if (typeof suiteOptions === "object") {
|
588
|
+
options = Object.assign({}, suiteOptions, options);
|
589
|
+
}
|
590
|
+
options.concurrent = this.concurrent || !this.sequential && (options === null || options === void 0 ? void 0 : options.concurrent);
|
591
|
+
options.sequential = this.sequential || !this.concurrent && (options === null || options === void 0 ? void 0 : options.sequential);
|
592
|
+
const test = task(formatName(name), {
|
593
|
+
...this,
|
594
|
+
...options,
|
595
|
+
handler
|
596
|
+
});
|
597
|
+
test.type = "test";
|
598
|
+
});
|
599
|
+
let collectorFixtures;
|
600
|
+
const collector = {
|
601
|
+
type: "collector",
|
602
|
+
name,
|
603
|
+
mode,
|
604
|
+
suite,
|
605
|
+
options: suiteOptions,
|
606
|
+
test,
|
607
|
+
tasks,
|
608
|
+
collect,
|
609
|
+
task,
|
610
|
+
clear,
|
611
|
+
on: addHook,
|
612
|
+
fixtures() {
|
613
|
+
return collectorFixtures;
|
614
|
+
},
|
615
|
+
scoped(fixtures) {
|
616
|
+
const parsed = mergeContextFixtures(fixtures, { fixtures: collectorFixtures }, (key) => {
|
617
|
+
var _getRunner$injectValu, _getRunner;
|
618
|
+
return (_getRunner$injectValu = (_getRunner = getRunner()).injectValue) === null || _getRunner$injectValu === void 0 ? void 0 : _getRunner$injectValu.call(_getRunner, key);
|
619
|
+
});
|
620
|
+
if (parsed.fixtures) {
|
621
|
+
collectorFixtures = parsed.fixtures;
|
622
|
+
}
|
623
|
+
}
|
624
|
+
};
|
625
|
+
function addHook(name, ...fn) {
|
626
|
+
getHooks(suite)[name].push(...fn);
|
627
|
+
}
|
628
|
+
function initSuite(includeLocation) {
|
629
|
+
var _collectorContext$cur2;
|
630
|
+
if (typeof suiteOptions === "number") {
|
631
|
+
suiteOptions = { timeout: suiteOptions };
|
632
|
+
}
|
633
|
+
suite = {
|
634
|
+
id: "",
|
635
|
+
type: "suite",
|
636
|
+
name,
|
637
|
+
suite: (_collectorContext$cur2 = collectorContext.currentSuite) === null || _collectorContext$cur2 === void 0 ? void 0 : _collectorContext$cur2.suite,
|
638
|
+
mode,
|
639
|
+
each,
|
640
|
+
file: undefined,
|
641
|
+
shuffle: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle,
|
642
|
+
tasks: [],
|
643
|
+
meta: Object.create(null),
|
644
|
+
concurrent: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.concurrent
|
645
|
+
};
|
646
|
+
if (runner && includeLocation && runner.config.includeTaskLocation) {
|
647
|
+
const limit = Error.stackTraceLimit;
|
648
|
+
Error.stackTraceLimit = 15;
|
649
|
+
const error = new Error("stacktrace").stack;
|
650
|
+
Error.stackTraceLimit = limit;
|
651
|
+
const stack = findTestFileStackTrace(error, suite.each ?? false);
|
652
|
+
if (stack) {
|
653
|
+
suite.location = stack;
|
654
|
+
}
|
655
|
+
}
|
656
|
+
setHooks(suite, createSuiteHooks());
|
657
|
+
}
|
658
|
+
function clear() {
|
659
|
+
tasks.length = 0;
|
660
|
+
initSuite(false);
|
661
|
+
}
|
662
|
+
async function collect(file) {
|
663
|
+
if (!file) {
|
664
|
+
throw new TypeError("File is required to collect tasks.");
|
665
|
+
}
|
666
|
+
if (factory) {
|
667
|
+
await runWithSuite(collector, () => factory(test));
|
668
|
+
}
|
669
|
+
const allChildren = [];
|
670
|
+
for (const i of tasks) {
|
671
|
+
allChildren.push(i.type === "collector" ? await i.collect(file) : i);
|
672
|
+
}
|
673
|
+
suite.file = file;
|
674
|
+
suite.tasks = allChildren;
|
675
|
+
allChildren.forEach((task) => {
|
676
|
+
task.file = file;
|
677
|
+
});
|
678
|
+
return suite;
|
679
|
+
}
|
680
|
+
collectTask(collector);
|
681
|
+
return collector;
|
570
682
|
}
|
571
683
|
function withAwaitAsyncAssertions(fn, task) {
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
684
|
+
return async (...args) => {
|
685
|
+
const fnResult = await fn(...args);
|
686
|
+
if (task.promises) {
|
687
|
+
const result = await Promise.allSettled(task.promises);
|
688
|
+
const errors = result.map((r) => r.status === "rejected" ? r.reason : undefined).filter(Boolean);
|
689
|
+
if (errors.length) {
|
690
|
+
throw errors;
|
691
|
+
}
|
692
|
+
}
|
693
|
+
return fnResult;
|
694
|
+
};
|
583
695
|
}
|
584
696
|
function createSuite() {
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
});
|
656
|
-
};
|
657
|
-
};
|
658
|
-
suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
|
659
|
-
suiteFn.runIf = (condition) => condition ? suite : suite.skip;
|
660
|
-
return createChainable(
|
661
|
-
["concurrent", "sequential", "shuffle", "skip", "only", "todo"],
|
662
|
-
suiteFn
|
663
|
-
);
|
697
|
+
function suiteFn(name, factoryOrOptions, optionsOrFactory) {
|
698
|
+
var _currentSuite$options;
|
699
|
+
const mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
|
700
|
+
const currentSuite = collectorContext.currentSuite || defaultSuite;
|
701
|
+
let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
|
702
|
+
const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
|
703
|
+
const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
|
704
|
+
options = {
|
705
|
+
...currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.options,
|
706
|
+
...options,
|
707
|
+
shuffle: this.shuffle ?? options.shuffle ?? (currentSuite === null || currentSuite === void 0 || (_currentSuite$options = currentSuite.options) === null || _currentSuite$options === void 0 ? void 0 : _currentSuite$options.shuffle) ?? (runner === null || runner === void 0 ? void 0 : runner.config.sequence.shuffle)
|
708
|
+
};
|
709
|
+
const isConcurrent = isConcurrentSpecified || options.concurrent && !isSequentialSpecified;
|
710
|
+
const isSequential = isSequentialSpecified || options.sequential && !isConcurrentSpecified;
|
711
|
+
options.concurrent = isConcurrent && !isSequential;
|
712
|
+
options.sequential = isSequential && !isConcurrent;
|
713
|
+
return createSuiteCollector(formatName(name), factory, mode, this.each, options);
|
714
|
+
}
|
715
|
+
suiteFn.each = function(cases, ...args) {
|
716
|
+
const suite = this.withContext();
|
717
|
+
this.setContext("each", true);
|
718
|
+
if (Array.isArray(cases) && args.length) {
|
719
|
+
cases = formatTemplateString(cases, args);
|
720
|
+
}
|
721
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
722
|
+
const _name = formatName(name);
|
723
|
+
const arrayOnlyCases = cases.every(Array.isArray);
|
724
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
725
|
+
const fnFirst = typeof optionsOrFn === "function" && typeof fnOrOptions === "object";
|
726
|
+
cases.forEach((i, idx) => {
|
727
|
+
const items = Array.isArray(i) ? i : [i];
|
728
|
+
if (fnFirst) {
|
729
|
+
if (arrayOnlyCases) {
|
730
|
+
suite(formatTitle(_name, items, idx), () => handler(...items), options);
|
731
|
+
} else {
|
732
|
+
suite(formatTitle(_name, items, idx), () => handler(i), options);
|
733
|
+
}
|
734
|
+
} else {
|
735
|
+
if (arrayOnlyCases) {
|
736
|
+
suite(formatTitle(_name, items, idx), options, () => handler(...items));
|
737
|
+
} else {
|
738
|
+
suite(formatTitle(_name, items, idx), options, () => handler(i));
|
739
|
+
}
|
740
|
+
}
|
741
|
+
});
|
742
|
+
this.setContext("each", undefined);
|
743
|
+
};
|
744
|
+
};
|
745
|
+
suiteFn.for = function(cases, ...args) {
|
746
|
+
if (Array.isArray(cases) && args.length) {
|
747
|
+
cases = formatTemplateString(cases, args);
|
748
|
+
}
|
749
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
750
|
+
const name_ = formatName(name);
|
751
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
752
|
+
cases.forEach((item, idx) => {
|
753
|
+
suite(formatTitle(name_, toArray(item), idx), options, () => handler(item));
|
754
|
+
});
|
755
|
+
};
|
756
|
+
};
|
757
|
+
suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
|
758
|
+
suiteFn.runIf = (condition) => condition ? suite : suite.skip;
|
759
|
+
return createChainable([
|
760
|
+
"concurrent",
|
761
|
+
"sequential",
|
762
|
+
"shuffle",
|
763
|
+
"skip",
|
764
|
+
"only",
|
765
|
+
"todo"
|
766
|
+
], suiteFn);
|
664
767
|
}
|
665
768
|
function createTaskCollector(fn, context) {
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
769
|
+
const taskFn = fn;
|
770
|
+
taskFn.each = function(cases, ...args) {
|
771
|
+
const test = this.withContext();
|
772
|
+
this.setContext("each", true);
|
773
|
+
if (Array.isArray(cases) && args.length) {
|
774
|
+
cases = formatTemplateString(cases, args);
|
775
|
+
}
|
776
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
777
|
+
const _name = formatName(name);
|
778
|
+
const arrayOnlyCases = cases.every(Array.isArray);
|
779
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
780
|
+
const fnFirst = typeof optionsOrFn === "function" && typeof fnOrOptions === "object";
|
781
|
+
cases.forEach((i, idx) => {
|
782
|
+
const items = Array.isArray(i) ? i : [i];
|
783
|
+
if (fnFirst) {
|
784
|
+
if (arrayOnlyCases) {
|
785
|
+
test(formatTitle(_name, items, idx), () => handler(...items), options);
|
786
|
+
} else {
|
787
|
+
test(formatTitle(_name, items, idx), () => handler(i), options);
|
788
|
+
}
|
789
|
+
} else {
|
790
|
+
if (arrayOnlyCases) {
|
791
|
+
test(formatTitle(_name, items, idx), options, () => handler(...items));
|
792
|
+
} else {
|
793
|
+
test(formatTitle(_name, items, idx), options, () => handler(i));
|
794
|
+
}
|
795
|
+
}
|
796
|
+
});
|
797
|
+
this.setContext("each", undefined);
|
798
|
+
};
|
799
|
+
};
|
800
|
+
taskFn.for = function(cases, ...args) {
|
801
|
+
const test = this.withContext();
|
802
|
+
if (Array.isArray(cases) && args.length) {
|
803
|
+
cases = formatTemplateString(cases, args);
|
804
|
+
}
|
805
|
+
return (name, optionsOrFn, fnOrOptions) => {
|
806
|
+
const _name = formatName(name);
|
807
|
+
const { options, handler } = parseArguments(optionsOrFn, fnOrOptions);
|
808
|
+
cases.forEach((item, idx) => {
|
809
|
+
const handlerWrapper = (ctx) => handler(item, ctx);
|
810
|
+
handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
|
811
|
+
handlerWrapper.toString = () => handler.toString();
|
812
|
+
test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
|
813
|
+
});
|
814
|
+
};
|
815
|
+
};
|
816
|
+
taskFn.skipIf = function(condition) {
|
817
|
+
return condition ? this.skip : this;
|
818
|
+
};
|
819
|
+
taskFn.runIf = function(condition) {
|
820
|
+
return condition ? this : this.skip;
|
821
|
+
};
|
822
|
+
taskFn.scoped = function(fixtures) {
|
823
|
+
const collector = getCurrentSuite();
|
824
|
+
collector.scoped(fixtures);
|
825
|
+
};
|
826
|
+
taskFn.extend = function(fixtures) {
|
827
|
+
const _context = mergeContextFixtures(fixtures, context || {}, (key) => {
|
828
|
+
var _getRunner$injectValu2, _getRunner2;
|
829
|
+
return (_getRunner$injectValu2 = (_getRunner2 = getRunner()).injectValue) === null || _getRunner$injectValu2 === void 0 ? void 0 : _getRunner$injectValu2.call(_getRunner2, key);
|
830
|
+
});
|
831
|
+
return createTest(function fn(name, optionsOrFn, optionsOrTest) {
|
832
|
+
const collector = getCurrentSuite();
|
833
|
+
const scopedFixtures = collector.fixtures();
|
834
|
+
if (scopedFixtures) {
|
835
|
+
this.fixtures = mergeScopedFixtures(this.fixtures || [], scopedFixtures);
|
836
|
+
}
|
837
|
+
collector.test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
|
838
|
+
}, _context);
|
839
|
+
};
|
840
|
+
const _test = createChainable([
|
841
|
+
"concurrent",
|
842
|
+
"sequential",
|
843
|
+
"skip",
|
844
|
+
"only",
|
845
|
+
"todo",
|
846
|
+
"fails"
|
847
|
+
], taskFn);
|
848
|
+
if (context) {
|
849
|
+
_test.mergeContext(context);
|
850
|
+
}
|
851
|
+
return _test;
|
749
852
|
}
|
750
853
|
function createTest(fn, context) {
|
751
|
-
|
854
|
+
return createTaskCollector(fn, context);
|
752
855
|
}
|
753
856
|
function formatName(name) {
|
754
|
-
|
857
|
+
return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
|
755
858
|
}
|
756
859
|
function formatTitle(template, items, idx) {
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
return formatted;
|
860
|
+
if (template.includes("%#") || template.includes("%$")) {
|
861
|
+
template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/%\$/g, `${idx + 1}`).replace(/__vitest_escaped_%__/g, "%%");
|
862
|
+
}
|
863
|
+
const count = template.split("%").length - 1;
|
864
|
+
if (template.includes("%f")) {
|
865
|
+
const placeholders = template.match(/%f/g) || [];
|
866
|
+
placeholders.forEach((_, i) => {
|
867
|
+
if (isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
|
868
|
+
let occurrence = 0;
|
869
|
+
template = template.replace(/%f/g, (match) => {
|
870
|
+
occurrence++;
|
871
|
+
return occurrence === i + 1 ? "-%f" : match;
|
872
|
+
});
|
873
|
+
}
|
874
|
+
});
|
875
|
+
}
|
876
|
+
let formatted = format(template, ...items.slice(0, count));
|
877
|
+
const isObjectItem = isObject(items[0]);
|
878
|
+
formatted = formatted.replace(/\$([$\w.]+)/g, (_, key) => {
|
879
|
+
var _runner$config;
|
880
|
+
const isArrayKey = /^\d+$/.test(key);
|
881
|
+
if (!isObjectItem && !isArrayKey) {
|
882
|
+
return `$${key}`;
|
883
|
+
}
|
884
|
+
const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
|
885
|
+
const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
|
886
|
+
return objDisplay(value, { truncate: runner === null || runner === void 0 || (_runner$config = runner.config) === null || _runner$config === void 0 || (_runner$config = _runner$config.chaiConfig) === null || _runner$config === void 0 ? void 0 : _runner$config.truncateThreshold });
|
887
|
+
});
|
888
|
+
return formatted;
|
787
889
|
}
|
788
890
|
function formatTemplateString(cases, args) {
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
891
|
+
const header = cases.join("").trim().replace(/ /g, "").split("\n").map((i) => i.split("|"))[0];
|
892
|
+
const res = [];
|
893
|
+
for (let i = 0; i < Math.floor(args.length / header.length); i++) {
|
894
|
+
const oneCase = {};
|
895
|
+
for (let j = 0; j < header.length; j++) {
|
896
|
+
oneCase[header[j]] = args[i * header.length + j];
|
897
|
+
}
|
898
|
+
res.push(oneCase);
|
899
|
+
}
|
900
|
+
return res;
|
799
901
|
}
|
800
902
|
function findTestFileStackTrace(error, each) {
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
* in source maps it's the same boundary, so it just points to the start of it
|
812
|
-
*/
|
813
|
-
column: each ? stack.column + 1 : stack.column
|
814
|
-
};
|
815
|
-
}
|
816
|
-
}
|
903
|
+
const lines = error.split("\n").slice(1);
|
904
|
+
for (const line of lines) {
|
905
|
+
const stack = parseSingleStack(line);
|
906
|
+
if (stack && stack.file === getTestFilepath()) {
|
907
|
+
return {
|
908
|
+
line: stack.line,
|
909
|
+
column: each ? stack.column + 1 : stack.column
|
910
|
+
};
|
911
|
+
}
|
912
|
+
}
|
817
913
|
}
|
818
914
|
|
819
915
|
function getDefaultHookTimeout() {
|
820
|
-
|
916
|
+
return getRunner().config.hookTimeout;
|
821
917
|
}
|
822
918
|
const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
|
823
919
|
const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
|
824
920
|
function getBeforeHookCleanupCallback(hook, result) {
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
}
|
921
|
+
if (typeof result === "function") {
|
922
|
+
const timeout = CLEANUP_TIMEOUT_KEY in hook && typeof hook[CLEANUP_TIMEOUT_KEY] === "number" ? hook[CLEANUP_TIMEOUT_KEY] : getDefaultHookTimeout();
|
923
|
+
const stackTraceError = CLEANUP_STACK_TRACE_KEY in hook && hook[CLEANUP_STACK_TRACE_KEY] instanceof Error ? hook[CLEANUP_STACK_TRACE_KEY] : undefined;
|
924
|
+
return withTimeout(result, timeout, true, stackTraceError);
|
925
|
+
}
|
926
|
+
}
|
927
|
+
/**
|
928
|
+
* Registers a callback function to be executed once before all tests within the current suite.
|
929
|
+
* 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.
|
930
|
+
*
|
931
|
+
* **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.
|
932
|
+
*
|
933
|
+
* @param {Function} fn - The callback function to be executed before all tests.
|
934
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
935
|
+
* @returns {void}
|
936
|
+
* @example
|
937
|
+
* ```ts
|
938
|
+
* // Example of using beforeAll to set up a database connection
|
939
|
+
* beforeAll(async () => {
|
940
|
+
* await database.connect();
|
941
|
+
* });
|
942
|
+
* ```
|
943
|
+
*/
|
831
944
|
function beforeAll(fn, timeout = getDefaultHookTimeout()) {
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
945
|
+
assertTypes(fn, "\"beforeAll\" callback", ["function"]);
|
946
|
+
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
947
|
+
return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
|
948
|
+
[CLEANUP_TIMEOUT_KEY]: timeout,
|
949
|
+
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
950
|
+
}));
|
951
|
+
}
|
952
|
+
/**
|
953
|
+
* Registers a callback function to be executed once after all tests within the current suite have completed.
|
954
|
+
* 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.
|
955
|
+
*
|
956
|
+
* **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.
|
957
|
+
*
|
958
|
+
* @param {Function} fn - The callback function to be executed after all tests.
|
959
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
960
|
+
* @returns {void}
|
961
|
+
* @example
|
962
|
+
* ```ts
|
963
|
+
* // Example of using afterAll to close a database connection
|
964
|
+
* afterAll(async () => {
|
965
|
+
* await database.disconnect();
|
966
|
+
* });
|
967
|
+
* ```
|
968
|
+
*/
|
850
969
|
function afterAll(fn, timeout) {
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
}
|
970
|
+
assertTypes(fn, "\"afterAll\" callback", ["function"]);
|
971
|
+
return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
|
972
|
+
}
|
973
|
+
/**
|
974
|
+
* Registers a callback function to be executed before each test within the current suite.
|
975
|
+
* 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.
|
976
|
+
*
|
977
|
+
* **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.
|
978
|
+
*
|
979
|
+
* @param {Function} fn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.
|
980
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
981
|
+
* @returns {void}
|
982
|
+
* @example
|
983
|
+
* ```ts
|
984
|
+
* // Example of using beforeEach to reset a database state
|
985
|
+
* beforeEach(async () => {
|
986
|
+
* await database.reset();
|
987
|
+
* });
|
988
|
+
* ```
|
989
|
+
*/
|
862
990
|
function beforeEach(fn, timeout = getDefaultHookTimeout()) {
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
991
|
+
assertTypes(fn, "\"beforeEach\" callback", ["function"]);
|
992
|
+
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
993
|
+
return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError), {
|
994
|
+
[CLEANUP_TIMEOUT_KEY]: timeout,
|
995
|
+
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
996
|
+
}));
|
997
|
+
}
|
998
|
+
/**
|
999
|
+
* Registers a callback function to be executed after each test within the current suite has completed.
|
1000
|
+
* 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.
|
1001
|
+
*
|
1002
|
+
* **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.
|
1003
|
+
*
|
1004
|
+
* @param {Function} fn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.
|
1005
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
1006
|
+
* @returns {void}
|
1007
|
+
* @example
|
1008
|
+
* ```ts
|
1009
|
+
* // Example of using afterEach to delete temporary files created during a test
|
1010
|
+
* afterEach(async () => {
|
1011
|
+
* await fileSystem.deleteTempFiles();
|
1012
|
+
* });
|
1013
|
+
* ```
|
1014
|
+
*/
|
881
1015
|
function afterEach(fn, timeout) {
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
}
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
);
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
1016
|
+
assertTypes(fn, "\"afterEach\" callback", ["function"]);
|
1017
|
+
return getCurrentSuite().on("afterEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
|
1018
|
+
}
|
1019
|
+
/**
|
1020
|
+
* Registers a callback function to be executed when a test fails within the current suite.
|
1021
|
+
* This function allows for custom actions to be performed in response to test failures, such as logging, cleanup, or additional diagnostics.
|
1022
|
+
*
|
1023
|
+
* **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.
|
1024
|
+
*
|
1025
|
+
* @param {Function} fn - The callback function to be executed upon a test failure. The function receives the test result (including errors).
|
1026
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
1027
|
+
* @throws {Error} Throws an error if the function is not called within a test.
|
1028
|
+
* @returns {void}
|
1029
|
+
* @example
|
1030
|
+
* ```ts
|
1031
|
+
* // Example of using onTestFailed to log failure details
|
1032
|
+
* onTestFailed(({ errors }) => {
|
1033
|
+
* console.log(`Test failed: ${test.name}`, errors);
|
1034
|
+
* });
|
1035
|
+
* ```
|
1036
|
+
*/
|
1037
|
+
const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
|
1038
|
+
test.onFailed || (test.onFailed = []);
|
1039
|
+
test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
|
1040
|
+
});
|
1041
|
+
/**
|
1042
|
+
* Registers a callback function to be executed when the current test finishes, regardless of the outcome (pass or fail).
|
1043
|
+
* This function is ideal for performing actions that should occur after every test execution, such as cleanup, logging, or resetting shared resources.
|
1044
|
+
*
|
1045
|
+
* 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`.
|
1046
|
+
*
|
1047
|
+
* **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.
|
1048
|
+
*
|
1049
|
+
* **Note:** The `onTestFinished` hook is not called if the test is canceled with a dynamic `ctx.skip()` call.
|
1050
|
+
*
|
1051
|
+
* @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.
|
1052
|
+
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
1053
|
+
* @throws {Error} Throws an error if the function is not called within a test.
|
1054
|
+
* @returns {void}
|
1055
|
+
* @example
|
1056
|
+
* ```ts
|
1057
|
+
* // Example of using onTestFinished for cleanup
|
1058
|
+
* const db = await connectToDatabase();
|
1059
|
+
* onTestFinished(async () => {
|
1060
|
+
* await db.disconnect();
|
1061
|
+
* });
|
1062
|
+
* ```
|
1063
|
+
*/
|
1064
|
+
const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
|
1065
|
+
test.onFinished || (test.onFinished = []);
|
1066
|
+
test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
|
1067
|
+
});
|
921
1068
|
function createTestHook(name, handler) {
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
1069
|
+
return (fn, timeout) => {
|
1070
|
+
assertTypes(fn, `"${name}" callback`, ["function"]);
|
1071
|
+
const current = getCurrentTest();
|
1072
|
+
if (!current) {
|
1073
|
+
throw new Error(`Hook ${name}() can only be called inside a test`);
|
1074
|
+
}
|
1075
|
+
return handler(current, fn, timeout);
|
1076
|
+
};
|
930
1077
|
}
|
931
1078
|
|
932
1079
|
async function runSetupFiles(config, files, runner) {
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
}
|
943
|
-
}
|
1080
|
+
if (config.sequence.setupFiles === "parallel") {
|
1081
|
+
await Promise.all(files.map(async (fsPath) => {
|
1082
|
+
await runner.importFile(fsPath, "setup");
|
1083
|
+
}));
|
1084
|
+
} else {
|
1085
|
+
for (const fsPath of files) {
|
1086
|
+
await runner.importFile(fsPath, "setup");
|
1087
|
+
}
|
1088
|
+
}
|
944
1089
|
}
|
945
1090
|
|
946
1091
|
const now$1 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
947
1092
|
async function collectTests(specs, runner) {
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
);
|
1005
|
-
if (file.mode === "queued") {
|
1006
|
-
file.mode = "run";
|
1007
|
-
}
|
1008
|
-
files.push(file);
|
1009
|
-
}
|
1010
|
-
return files;
|
1093
|
+
const files = [];
|
1094
|
+
const config = runner.config;
|
1095
|
+
for (const spec of specs) {
|
1096
|
+
var _runner$onCollectStar;
|
1097
|
+
const filepath = typeof spec === "string" ? spec : spec.filepath;
|
1098
|
+
const testLocations = typeof spec === "string" ? undefined : spec.testLocations;
|
1099
|
+
const file = createFileTask(filepath, config.root, config.name, runner.pool);
|
1100
|
+
file.shuffle = config.sequence.shuffle;
|
1101
|
+
(_runner$onCollectStar = runner.onCollectStart) === null || _runner$onCollectStar === void 0 ? void 0 : _runner$onCollectStar.call(runner, file);
|
1102
|
+
clearCollectorContext(filepath, runner);
|
1103
|
+
try {
|
1104
|
+
const setupFiles = toArray(config.setupFiles);
|
1105
|
+
if (setupFiles.length) {
|
1106
|
+
const setupStart = now$1();
|
1107
|
+
await runSetupFiles(config, setupFiles, runner);
|
1108
|
+
const setupEnd = now$1();
|
1109
|
+
file.setupDuration = setupEnd - setupStart;
|
1110
|
+
} else {
|
1111
|
+
file.setupDuration = 0;
|
1112
|
+
}
|
1113
|
+
const collectStart = now$1();
|
1114
|
+
await runner.importFile(filepath, "collect");
|
1115
|
+
const defaultTasks = await getDefaultSuite().collect(file);
|
1116
|
+
const fileHooks = createSuiteHooks();
|
1117
|
+
mergeHooks(fileHooks, getHooks(defaultTasks));
|
1118
|
+
for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
|
1119
|
+
if (c.type === "test" || c.type === "suite") {
|
1120
|
+
file.tasks.push(c);
|
1121
|
+
} else if (c.type === "collector") {
|
1122
|
+
const suite = await c.collect(file);
|
1123
|
+
if (suite.name || suite.tasks.length) {
|
1124
|
+
mergeHooks(fileHooks, getHooks(suite));
|
1125
|
+
file.tasks.push(suite);
|
1126
|
+
}
|
1127
|
+
} else {
|
1128
|
+
c;
|
1129
|
+
}
|
1130
|
+
}
|
1131
|
+
setHooks(file, fileHooks);
|
1132
|
+
file.collectDuration = now$1() - collectStart;
|
1133
|
+
} catch (e) {
|
1134
|
+
const error = processError(e);
|
1135
|
+
file.result = {
|
1136
|
+
state: "fail",
|
1137
|
+
errors: [error]
|
1138
|
+
};
|
1139
|
+
}
|
1140
|
+
calculateSuiteHash(file);
|
1141
|
+
const hasOnlyTasks = someTasksAreOnly(file);
|
1142
|
+
interpretTaskModes(file, config.testNamePattern, testLocations, hasOnlyTasks, false, config.allowOnly);
|
1143
|
+
if (file.mode === "queued") {
|
1144
|
+
file.mode = "run";
|
1145
|
+
}
|
1146
|
+
files.push(file);
|
1147
|
+
}
|
1148
|
+
return files;
|
1011
1149
|
}
|
1012
1150
|
function mergeHooks(baseHooks, hooks) {
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1151
|
+
for (const _key in hooks) {
|
1152
|
+
const key = _key;
|
1153
|
+
baseHooks[key].push(...hooks[key]);
|
1154
|
+
}
|
1155
|
+
return baseHooks;
|
1018
1156
|
}
|
1019
1157
|
|
1020
1158
|
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
1021
1159
|
const unixNow = Date.now;
|
1022
1160
|
function updateSuiteHookState(task, name, state, runner) {
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
task,
|
1039
|
-
runner
|
1040
|
-
);
|
1041
|
-
}
|
1161
|
+
if (!task.result) {
|
1162
|
+
task.result = { state: "run" };
|
1163
|
+
}
|
1164
|
+
if (!task.result.hooks) {
|
1165
|
+
task.result.hooks = {};
|
1166
|
+
}
|
1167
|
+
const suiteHooks = task.result.hooks;
|
1168
|
+
if (suiteHooks) {
|
1169
|
+
suiteHooks[name] = state;
|
1170
|
+
let event = state === "run" ? "before-hook-start" : "before-hook-end";
|
1171
|
+
if (name === "afterAll" || name === "afterEach") {
|
1172
|
+
event = state === "run" ? "after-hook-start" : "after-hook-end";
|
1173
|
+
}
|
1174
|
+
updateTask(event, task, runner);
|
1175
|
+
}
|
1042
1176
|
}
|
1043
1177
|
function getSuiteHooks(suite, name, sequence) {
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1178
|
+
const hooks = getHooks(suite)[name];
|
1179
|
+
if (sequence === "stack" && (name === "afterAll" || name === "afterEach")) {
|
1180
|
+
return hooks.slice().reverse();
|
1181
|
+
}
|
1182
|
+
return hooks;
|
1049
1183
|
}
|
1050
1184
|
async function callTestHooks(runner, test, hooks, sequence) {
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1185
|
+
if (sequence === "stack") {
|
1186
|
+
hooks = hooks.slice().reverse();
|
1187
|
+
}
|
1188
|
+
if (!hooks.length) {
|
1189
|
+
return;
|
1190
|
+
}
|
1191
|
+
const onTestFailed = test.context.onTestFailed;
|
1192
|
+
const onTestFinished = test.context.onTestFinished;
|
1193
|
+
test.context.onTestFailed = () => {
|
1194
|
+
throw new Error(`Cannot call "onTestFailed" inside a test hook.`);
|
1195
|
+
};
|
1196
|
+
test.context.onTestFinished = () => {
|
1197
|
+
throw new Error(`Cannot call "onTestFinished" inside a test hook.`);
|
1198
|
+
};
|
1199
|
+
if (sequence === "parallel") {
|
1200
|
+
try {
|
1201
|
+
await Promise.all(hooks.map((fn) => fn(test.context)));
|
1202
|
+
} catch (e) {
|
1203
|
+
failTask(test.result, e, runner.config.diffOptions);
|
1204
|
+
}
|
1205
|
+
} else {
|
1206
|
+
for (const fn of hooks) {
|
1207
|
+
try {
|
1208
|
+
await fn(test.context);
|
1209
|
+
} catch (e) {
|
1210
|
+
failTask(test.result, e, runner.config.diffOptions);
|
1211
|
+
}
|
1212
|
+
}
|
1213
|
+
}
|
1214
|
+
test.context.onTestFailed = onTestFailed;
|
1215
|
+
test.context.onTestFinished = onTestFinished;
|
1082
1216
|
}
|
1083
1217
|
async function callSuiteHook(suite, currentTask, name, runner, args) {
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
...await callSuiteHook(parentSuite, currentTask, name, runner, args)
|
1114
|
-
);
|
1115
|
-
}
|
1116
|
-
return callbacks;
|
1117
|
-
}
|
1118
|
-
const packs = /* @__PURE__ */ new Map();
|
1218
|
+
const sequence = runner.config.sequence.hooks;
|
1219
|
+
const callbacks = [];
|
1220
|
+
const parentSuite = "filepath" in suite ? null : suite.suite || suite.file;
|
1221
|
+
if (name === "beforeEach" && parentSuite) {
|
1222
|
+
callbacks.push(...await callSuiteHook(parentSuite, currentTask, name, runner, args));
|
1223
|
+
}
|
1224
|
+
const hooks = getSuiteHooks(suite, name, sequence);
|
1225
|
+
if (hooks.length > 0) {
|
1226
|
+
updateSuiteHookState(currentTask, name, "run", runner);
|
1227
|
+
}
|
1228
|
+
async function runHook(hook) {
|
1229
|
+
return getBeforeHookCleanupCallback(hook, await hook(...args));
|
1230
|
+
}
|
1231
|
+
if (sequence === "parallel") {
|
1232
|
+
callbacks.push(...await Promise.all(hooks.map((hook) => runHook(hook))));
|
1233
|
+
} else {
|
1234
|
+
for (const hook of hooks) {
|
1235
|
+
callbacks.push(await runHook(hook));
|
1236
|
+
}
|
1237
|
+
}
|
1238
|
+
if (hooks.length > 0) {
|
1239
|
+
updateSuiteHookState(currentTask, name, "pass", runner);
|
1240
|
+
}
|
1241
|
+
if (name === "afterEach" && parentSuite) {
|
1242
|
+
callbacks.push(...await callSuiteHook(parentSuite, currentTask, name, runner, args));
|
1243
|
+
}
|
1244
|
+
return callbacks;
|
1245
|
+
}
|
1246
|
+
const packs = new Map();
|
1119
1247
|
const eventsPacks = [];
|
1120
1248
|
let updateTimer;
|
1121
1249
|
let previousUpdate;
|
1122
1250
|
function updateTask(event, task, runner) {
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1251
|
+
eventsPacks.push([task.id, event]);
|
1252
|
+
packs.set(task.id, [task.result, task.meta]);
|
1253
|
+
const { clearTimeout, setTimeout } = getSafeTimers();
|
1254
|
+
clearTimeout(updateTimer);
|
1255
|
+
updateTimer = setTimeout(() => {
|
1256
|
+
previousUpdate = sendTasksUpdate(runner);
|
1257
|
+
}, 10);
|
1130
1258
|
}
|
1131
1259
|
async function sendTasksUpdate(runner) {
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1260
|
+
const { clearTimeout } = getSafeTimers();
|
1261
|
+
clearTimeout(updateTimer);
|
1262
|
+
await previousUpdate;
|
1263
|
+
if (packs.size) {
|
1264
|
+
var _runner$onTaskUpdate;
|
1265
|
+
const taskPacks = Array.from(packs).map(([id, task]) => {
|
1266
|
+
return [
|
1267
|
+
id,
|
1268
|
+
task[0],
|
1269
|
+
task[1]
|
1270
|
+
];
|
1271
|
+
});
|
1272
|
+
const p = (_runner$onTaskUpdate = runner.onTaskUpdate) === null || _runner$onTaskUpdate === void 0 ? void 0 : _runner$onTaskUpdate.call(runner, taskPacks, eventsPacks);
|
1273
|
+
eventsPacks.length = 0;
|
1274
|
+
packs.clear();
|
1275
|
+
return p;
|
1276
|
+
}
|
1145
1277
|
}
|
1146
1278
|
async function callCleanupHooks(cleanups) {
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
})
|
1154
|
-
);
|
1279
|
+
await Promise.all(cleanups.map(async (fn) => {
|
1280
|
+
if (typeof fn !== "function") {
|
1281
|
+
return;
|
1282
|
+
}
|
1283
|
+
await fn();
|
1284
|
+
}));
|
1155
1285
|
}
|
1156
1286
|
async function runTest(test, runner) {
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
test.result.state = "pass";
|
1268
|
-
test.result.errors = void 0;
|
1269
|
-
}
|
1270
|
-
}
|
1271
|
-
setCurrentTest(void 0);
|
1272
|
-
test.result.duration = now() - start;
|
1273
|
-
await ((_i = runner.onAfterRunTask) == null ? void 0 : _i.call(runner, test));
|
1274
|
-
updateTask("test-finished", test, runner);
|
1287
|
+
var _runner$onBeforeRunTa, _test$result, _runner$onAfterRunTas;
|
1288
|
+
await ((_runner$onBeforeRunTa = runner.onBeforeRunTask) === null || _runner$onBeforeRunTa === void 0 ? void 0 : _runner$onBeforeRunTa.call(runner, test));
|
1289
|
+
if (test.mode !== "run" && test.mode !== "queued") {
|
1290
|
+
return;
|
1291
|
+
}
|
1292
|
+
if (((_test$result = test.result) === null || _test$result === void 0 ? void 0 : _test$result.state) === "fail") {
|
1293
|
+
updateTask("test-failed-early", test, runner);
|
1294
|
+
return;
|
1295
|
+
}
|
1296
|
+
const start = now();
|
1297
|
+
test.result = {
|
1298
|
+
state: "run",
|
1299
|
+
startTime: unixNow(),
|
1300
|
+
retryCount: 0
|
1301
|
+
};
|
1302
|
+
updateTask("test-prepare", test, runner);
|
1303
|
+
setCurrentTest(test);
|
1304
|
+
const suite = test.suite || test.file;
|
1305
|
+
const repeats = test.repeats ?? 0;
|
1306
|
+
for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
|
1307
|
+
const retry = test.retry ?? 0;
|
1308
|
+
for (let retryCount = 0; retryCount <= retry; retryCount++) {
|
1309
|
+
var _test$result2, _test$result3;
|
1310
|
+
let beforeEachCleanups = [];
|
1311
|
+
try {
|
1312
|
+
var _runner$onBeforeTryTa, _runner$onAfterTryTas;
|
1313
|
+
await ((_runner$onBeforeTryTa = runner.onBeforeTryTask) === null || _runner$onBeforeTryTa === void 0 ? void 0 : _runner$onBeforeTryTa.call(runner, test, {
|
1314
|
+
retry: retryCount,
|
1315
|
+
repeats: repeatCount
|
1316
|
+
}));
|
1317
|
+
test.result.repeatCount = repeatCount;
|
1318
|
+
beforeEachCleanups = await callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]);
|
1319
|
+
if (runner.runTask) {
|
1320
|
+
await runner.runTask(test);
|
1321
|
+
} else {
|
1322
|
+
const fn = getFn(test);
|
1323
|
+
if (!fn) {
|
1324
|
+
throw new Error("Test function is not found. Did you add it using `setFn`?");
|
1325
|
+
}
|
1326
|
+
await fn();
|
1327
|
+
}
|
1328
|
+
await ((_runner$onAfterTryTas = runner.onAfterTryTask) === null || _runner$onAfterTryTas === void 0 ? void 0 : _runner$onAfterTryTas.call(runner, test, {
|
1329
|
+
retry: retryCount,
|
1330
|
+
repeats: repeatCount
|
1331
|
+
}));
|
1332
|
+
if (test.result.state !== "fail") {
|
1333
|
+
if (!test.repeats) {
|
1334
|
+
test.result.state = "pass";
|
1335
|
+
} else if (test.repeats && retry === retryCount) {
|
1336
|
+
test.result.state = "pass";
|
1337
|
+
}
|
1338
|
+
}
|
1339
|
+
} catch (e) {
|
1340
|
+
failTask(test.result, e, runner.config.diffOptions);
|
1341
|
+
}
|
1342
|
+
if (((_test$result2 = test.result) === null || _test$result2 === void 0 ? void 0 : _test$result2.pending) || ((_test$result3 = test.result) === null || _test$result3 === void 0 ? void 0 : _test$result3.state) === "skip") {
|
1343
|
+
var _test$result4;
|
1344
|
+
test.mode = "skip";
|
1345
|
+
test.result = {
|
1346
|
+
state: "skip",
|
1347
|
+
note: (_test$result4 = test.result) === null || _test$result4 === void 0 ? void 0 : _test$result4.note,
|
1348
|
+
pending: true
|
1349
|
+
};
|
1350
|
+
updateTask("test-finished", test, runner);
|
1351
|
+
setCurrentTest(undefined);
|
1352
|
+
return;
|
1353
|
+
}
|
1354
|
+
try {
|
1355
|
+
var _runner$onTaskFinishe;
|
1356
|
+
await ((_runner$onTaskFinishe = runner.onTaskFinished) === null || _runner$onTaskFinishe === void 0 ? void 0 : _runner$onTaskFinishe.call(runner, test));
|
1357
|
+
} catch (e) {
|
1358
|
+
failTask(test.result, e, runner.config.diffOptions);
|
1359
|
+
}
|
1360
|
+
try {
|
1361
|
+
await callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]);
|
1362
|
+
await callCleanupHooks(beforeEachCleanups);
|
1363
|
+
await callFixtureCleanup(test.context);
|
1364
|
+
} catch (e) {
|
1365
|
+
failTask(test.result, e, runner.config.diffOptions);
|
1366
|
+
}
|
1367
|
+
await callTestHooks(runner, test, test.onFinished || [], "stack");
|
1368
|
+
if (test.result.state === "fail") {
|
1369
|
+
await callTestHooks(runner, test, test.onFailed || [], runner.config.sequence.hooks);
|
1370
|
+
}
|
1371
|
+
test.onFailed = undefined;
|
1372
|
+
test.onFinished = undefined;
|
1373
|
+
if (test.result.state === "pass") {
|
1374
|
+
break;
|
1375
|
+
}
|
1376
|
+
if (retryCount < retry) {
|
1377
|
+
test.result.state = "run";
|
1378
|
+
test.result.retryCount = (test.result.retryCount ?? 0) + 1;
|
1379
|
+
}
|
1380
|
+
updateTask("test-retried", test, runner);
|
1381
|
+
}
|
1382
|
+
}
|
1383
|
+
if (test.fails) {
|
1384
|
+
if (test.result.state === "pass") {
|
1385
|
+
const error = processError(new Error("Expect test to fail"));
|
1386
|
+
test.result.state = "fail";
|
1387
|
+
test.result.errors = [error];
|
1388
|
+
} else {
|
1389
|
+
test.result.state = "pass";
|
1390
|
+
test.result.errors = undefined;
|
1391
|
+
}
|
1392
|
+
}
|
1393
|
+
setCurrentTest(undefined);
|
1394
|
+
test.result.duration = now() - start;
|
1395
|
+
await ((_runner$onAfterRunTas = runner.onAfterRunTask) === null || _runner$onAfterRunTas === void 0 ? void 0 : _runner$onAfterRunTas.call(runner, test));
|
1396
|
+
updateTask("test-finished", test, runner);
|
1275
1397
|
}
|
1276
1398
|
function failTask(result, err, diffOptions) {
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1399
|
+
if (err instanceof PendingError) {
|
1400
|
+
result.state = "skip";
|
1401
|
+
result.note = err.note;
|
1402
|
+
result.pending = true;
|
1403
|
+
return;
|
1404
|
+
}
|
1405
|
+
result.state = "fail";
|
1406
|
+
const errors = Array.isArray(err) ? err : [err];
|
1407
|
+
for (const e of errors) {
|
1408
|
+
const error = processError(e, diffOptions);
|
1409
|
+
result.errors ?? (result.errors = []);
|
1410
|
+
result.errors.push(error);
|
1411
|
+
}
|
1290
1412
|
}
|
1291
1413
|
function markTasksAsSkipped(suite, runner) {
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1414
|
+
suite.tasks.forEach((t) => {
|
1415
|
+
t.mode = "skip";
|
1416
|
+
t.result = {
|
1417
|
+
...t.result,
|
1418
|
+
state: "skip"
|
1419
|
+
};
|
1420
|
+
updateTask("test-finished", t, runner);
|
1421
|
+
if (t.type === "suite") {
|
1422
|
+
markTasksAsSkipped(t, runner);
|
1423
|
+
}
|
1424
|
+
});
|
1300
1425
|
}
|
1301
1426
|
async function runSuite(suite, runner) {
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
} else if (hasFailed(suite)) {
|
1380
|
-
suite.result.state = "fail";
|
1381
|
-
} else {
|
1382
|
-
suite.result.state = "pass";
|
1383
|
-
}
|
1384
|
-
}
|
1385
|
-
suite.result.duration = now() - start;
|
1386
|
-
updateTask("suite-finished", suite, runner);
|
1387
|
-
await ((_d = runner.onAfterRunSuite) == null ? void 0 : _d.call(runner, suite));
|
1388
|
-
}
|
1427
|
+
var _runner$onBeforeRunSu, _suite$result;
|
1428
|
+
await ((_runner$onBeforeRunSu = runner.onBeforeRunSuite) === null || _runner$onBeforeRunSu === void 0 ? void 0 : _runner$onBeforeRunSu.call(runner, suite));
|
1429
|
+
if (((_suite$result = suite.result) === null || _suite$result === void 0 ? void 0 : _suite$result.state) === "fail") {
|
1430
|
+
markTasksAsSkipped(suite, runner);
|
1431
|
+
updateTask("suite-failed-early", suite, runner);
|
1432
|
+
return;
|
1433
|
+
}
|
1434
|
+
const start = now();
|
1435
|
+
const mode = suite.mode;
|
1436
|
+
suite.result = {
|
1437
|
+
state: mode === "skip" || mode === "todo" ? mode : "run",
|
1438
|
+
startTime: unixNow()
|
1439
|
+
};
|
1440
|
+
updateTask("suite-prepare", suite, runner);
|
1441
|
+
let beforeAllCleanups = [];
|
1442
|
+
if (suite.mode === "skip") {
|
1443
|
+
suite.result.state = "skip";
|
1444
|
+
updateTask("suite-finished", suite, runner);
|
1445
|
+
} else if (suite.mode === "todo") {
|
1446
|
+
suite.result.state = "todo";
|
1447
|
+
updateTask("suite-finished", suite, runner);
|
1448
|
+
} else {
|
1449
|
+
var _runner$onAfterRunSui;
|
1450
|
+
try {
|
1451
|
+
try {
|
1452
|
+
beforeAllCleanups = await callSuiteHook(suite, suite, "beforeAll", runner, [suite]);
|
1453
|
+
} catch (e) {
|
1454
|
+
markTasksAsSkipped(suite, runner);
|
1455
|
+
throw e;
|
1456
|
+
}
|
1457
|
+
if (runner.runSuite) {
|
1458
|
+
await runner.runSuite(suite);
|
1459
|
+
} else {
|
1460
|
+
for (let tasksGroup of partitionSuiteChildren(suite)) {
|
1461
|
+
if (tasksGroup[0].concurrent === true) {
|
1462
|
+
await Promise.all(tasksGroup.map((c) => runSuiteChild(c, runner)));
|
1463
|
+
} else {
|
1464
|
+
const { sequence } = runner.config;
|
1465
|
+
if (suite.shuffle) {
|
1466
|
+
const suites = tasksGroup.filter((group) => group.type === "suite");
|
1467
|
+
const tests = tasksGroup.filter((group) => group.type === "test");
|
1468
|
+
const groups = shuffle([suites, tests], sequence.seed);
|
1469
|
+
tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
|
1470
|
+
}
|
1471
|
+
for (const c of tasksGroup) {
|
1472
|
+
await runSuiteChild(c, runner);
|
1473
|
+
}
|
1474
|
+
}
|
1475
|
+
}
|
1476
|
+
}
|
1477
|
+
} catch (e) {
|
1478
|
+
failTask(suite.result, e, runner.config.diffOptions);
|
1479
|
+
}
|
1480
|
+
try {
|
1481
|
+
await callSuiteHook(suite, suite, "afterAll", runner, [suite]);
|
1482
|
+
await callCleanupHooks(beforeAllCleanups);
|
1483
|
+
} catch (e) {
|
1484
|
+
failTask(suite.result, e, runner.config.diffOptions);
|
1485
|
+
}
|
1486
|
+
if (suite.mode === "run" || suite.mode === "queued") {
|
1487
|
+
if (!runner.config.passWithNoTests && !hasTests(suite)) {
|
1488
|
+
var _suite$result$errors;
|
1489
|
+
suite.result.state = "fail";
|
1490
|
+
if (!((_suite$result$errors = suite.result.errors) === null || _suite$result$errors === void 0 ? void 0 : _suite$result$errors.length)) {
|
1491
|
+
const error = processError(new Error(`No test found in suite ${suite.name}`));
|
1492
|
+
suite.result.errors = [error];
|
1493
|
+
}
|
1494
|
+
} else if (hasFailed(suite)) {
|
1495
|
+
suite.result.state = "fail";
|
1496
|
+
} else {
|
1497
|
+
suite.result.state = "pass";
|
1498
|
+
}
|
1499
|
+
}
|
1500
|
+
suite.result.duration = now() - start;
|
1501
|
+
updateTask("suite-finished", suite, runner);
|
1502
|
+
await ((_runner$onAfterRunSui = runner.onAfterRunSuite) === null || _runner$onAfterRunSui === void 0 ? void 0 : _runner$onAfterRunSui.call(runner, suite));
|
1503
|
+
}
|
1389
1504
|
}
|
1390
1505
|
let limitMaxConcurrency;
|
1391
1506
|
async function runSuiteChild(c, runner) {
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1507
|
+
if (c.type === "test") {
|
1508
|
+
return limitMaxConcurrency(() => runTest(c, runner));
|
1509
|
+
} else if (c.type === "suite") {
|
1510
|
+
return runSuite(c, runner);
|
1511
|
+
}
|
1397
1512
|
}
|
1398
1513
|
async function runFiles(files, runner) {
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
await runSuite(file, runner);
|
1414
|
-
}
|
1514
|
+
limitMaxConcurrency ?? (limitMaxConcurrency = limitConcurrency(runner.config.maxConcurrency));
|
1515
|
+
for (const file of files) {
|
1516
|
+
if (!file.tasks.length && !runner.config.passWithNoTests) {
|
1517
|
+
var _file$result;
|
1518
|
+
if (!((_file$result = file.result) === null || _file$result === void 0 || (_file$result = _file$result.errors) === null || _file$result === void 0 ? void 0 : _file$result.length)) {
|
1519
|
+
const error = processError(new Error(`No test suite found in file ${file.filepath}`));
|
1520
|
+
file.result = {
|
1521
|
+
state: "fail",
|
1522
|
+
errors: [error]
|
1523
|
+
};
|
1524
|
+
}
|
1525
|
+
}
|
1526
|
+
await runSuite(file, runner);
|
1527
|
+
}
|
1415
1528
|
}
|
1416
1529
|
async function startTests(specs, runner) {
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1530
|
+
var _runner$onBeforeColle, _runner$onCollected, _runner$onBeforeRunFi, _runner$onAfterRunFil;
|
1531
|
+
const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
|
1532
|
+
await ((_runner$onBeforeColle = runner.onBeforeCollect) === null || _runner$onBeforeColle === void 0 ? void 0 : _runner$onBeforeColle.call(runner, paths));
|
1533
|
+
const files = await collectTests(specs, runner);
|
1534
|
+
await ((_runner$onCollected = runner.onCollected) === null || _runner$onCollected === void 0 ? void 0 : _runner$onCollected.call(runner, files));
|
1535
|
+
await ((_runner$onBeforeRunFi = runner.onBeforeRunFiles) === null || _runner$onBeforeRunFi === void 0 ? void 0 : _runner$onBeforeRunFi.call(runner, files));
|
1536
|
+
await runFiles(files, runner);
|
1537
|
+
await ((_runner$onAfterRunFil = runner.onAfterRunFiles) === null || _runner$onAfterRunFil === void 0 ? void 0 : _runner$onAfterRunFil.call(runner, files));
|
1538
|
+
await sendTasksUpdate(runner);
|
1539
|
+
return files;
|
1427
1540
|
}
|
1428
1541
|
async function publicCollect(specs, runner) {
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1542
|
+
var _runner$onBeforeColle2, _runner$onCollected2;
|
1543
|
+
const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
|
1544
|
+
await ((_runner$onBeforeColle2 = runner.onBeforeCollect) === null || _runner$onBeforeColle2 === void 0 ? void 0 : _runner$onBeforeColle2.call(runner, paths));
|
1545
|
+
const files = await collectTests(specs, runner);
|
1546
|
+
await ((_runner$onCollected2 = runner.onCollected) === null || _runner$onCollected2 === void 0 ? void 0 : _runner$onCollected2.call(runner, files));
|
1547
|
+
return files;
|
1435
1548
|
}
|
1436
1549
|
|
1437
1550
|
export { afterAll, afterEach, beforeAll, beforeEach, publicCollect as collectTests, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, setFn, setHooks, startTests, suite, test, updateTask };
|