|
- import limit from 'p-limit';
- import { getSafeTimers, format, isObject, objDisplay, objectAttr, noop, toArray, shuffle } from '@vitest/utils';
- import { c as createChainable, g as generateHash, p as processError, a as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, b as partitionSuiteChildren, h as hasTests, d as hasFailed } from './chunk-tasks.js';
- import { relative } from 'pathe';
-
- const fnMap = /* @__PURE__ */ new WeakMap();
- const hooksMap = /* @__PURE__ */ new WeakMap();
- function setFn(key, fn) {
- fnMap.set(key, fn);
- }
- function getFn(key) {
- return fnMap.get(key);
- }
- function setHooks(key, hooks) {
- hooksMap.set(key, hooks);
- }
- function getHooks(key) {
- return hooksMap.get(key);
- }
-
- const collectorContext = {
- tasks: [],
- currentSuite: null
- };
- function collectTask(task) {
- var _a;
- (_a = collectorContext.currentSuite) == null ? void 0 : _a.tasks.push(task);
- }
- async function runWithSuite(suite, fn) {
- const prev = collectorContext.currentSuite;
- collectorContext.currentSuite = suite;
- await fn();
- collectorContext.currentSuite = prev;
- }
- function withTimeout(fn, timeout, isHook = false) {
- if (timeout <= 0 || timeout === Infinity)
- return fn;
- const { setTimeout, clearTimeout } = getSafeTimers();
- return (...args) => {
- return Promise.race([fn(...args), new Promise((resolve, reject) => {
- var _a;
- const timer = setTimeout(() => {
- clearTimeout(timer);
- reject(new Error(makeTimeoutMsg(isHook, timeout)));
- }, timeout);
- (_a = timer.unref) == null ? void 0 : _a.call(timer);
- })]);
- };
- }
- function createTestContext(test, runner) {
- var _a;
- const context = function() {
- throw new Error("done() callback is deprecated, use promise instead");
- };
- context.meta = test;
- context.onTestFailed = (fn) => {
- test.onFailed || (test.onFailed = []);
- test.onFailed.push(fn);
- };
- return ((_a = runner.extendTestContext) == null ? void 0 : _a.call(runner, context)) || context;
- }
- function makeTimeoutMsg(isHook, timeout) {
- return `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.
- If this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
- }
-
- const suite = createSuite();
- const test = createTest(
- function(name, fn, options) {
- getCurrentSuite().test.fn.call(this, name, fn, options);
- }
- );
- const describe = suite;
- const it = test;
- let runner;
- let defaultSuite;
- function getDefaultSuite() {
- return defaultSuite;
- }
- function getRunner() {
- return runner;
- }
- function clearCollectorContext(currentRunner) {
- if (!defaultSuite)
- defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle("") : suite("");
- runner = currentRunner;
- collectorContext.tasks.length = 0;
- defaultSuite.clear();
- collectorContext.currentSuite = defaultSuite;
- }
- function getCurrentSuite() {
- return collectorContext.currentSuite || defaultSuite;
- }
- function createSuiteHooks() {
- return {
- beforeAll: [],
- afterAll: [],
- beforeEach: [],
- afterEach: []
- };
- }
- function createSuiteCollector(name, factory = () => {
- }, mode, concurrent, shuffle, suiteOptions) {
- const tasks = [];
- const factoryQueue = [];
- let suite2;
- initSuite();
- const test2 = createTest(function(name2, fn = noop, options = suiteOptions) {
- const mode2 = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
- if (typeof options === "number")
- options = { timeout: options };
- const test3 = {
- id: "",
- type: "test",
- name: name2,
- mode: mode2,
- suite: void 0,
- fails: this.fails,
- retry: options == null ? void 0 : options.retry
- };
- if (this.concurrent || concurrent)
- test3.concurrent = true;
- if (shuffle)
- test3.shuffle = true;
- const context = createTestContext(test3, runner);
- Object.defineProperty(test3, "context", {
- value: context,
- enumerable: false
- });
- setFn(test3, withTimeout(
- () => fn(context),
- (options == null ? void 0 : options.timeout) ?? runner.config.testTimeout
- ));
- tasks.push(test3);
- });
- const custom = function(name2 = "") {
- const self = this || {};
- const task = {
- id: "",
- name: name2,
- type: "custom",
- mode: self.only ? "only" : self.skip ? "skip" : self.todo ? "todo" : "run"
- };
- tasks.push(task);
- return task;
- };
- const collector = {
- type: "collector",
- name,
- mode,
- test: test2,
- tasks,
- collect,
- custom,
- clear,
- on: addHook
- };
- function addHook(name2, ...fn) {
- getHooks(suite2)[name2].push(...fn);
- }
- function initSuite() {
- suite2 = {
- id: "",
- type: "suite",
- name,
- mode,
- shuffle,
- tasks: []
- };
- setHooks(suite2, createSuiteHooks());
- }
- function clear() {
- tasks.length = 0;
- factoryQueue.length = 0;
- initSuite();
- }
- async function collect(file) {
- factoryQueue.length = 0;
- if (factory)
- await runWithSuite(collector, () => factory(test2));
- const allChildren = [];
- for (const i of [...factoryQueue, ...tasks])
- allChildren.push(i.type === "collector" ? await i.collect(file) : i);
- suite2.file = file;
- suite2.tasks = allChildren;
- allChildren.forEach((task) => {
- task.suite = suite2;
- if (file)
- task.file = file;
- });
- return suite2;
- }
- collectTask(collector);
- return collector;
- }
- function createSuite() {
- function suiteFn(name, factory, options) {
- const mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
- return createSuiteCollector(name, factory, mode, this.concurrent, this.shuffle, options);
- }
- suiteFn.each = function(cases, ...args) {
- const suite2 = this.withContext();
- if (Array.isArray(cases) && args.length)
- cases = formatTemplateString(cases, args);
- return (name, fn, options) => {
- const arrayOnlyCases = cases.every(Array.isArray);
- cases.forEach((i, idx) => {
- const items = Array.isArray(i) ? i : [i];
- arrayOnlyCases ? suite2(formatTitle(name, items, idx), () => fn(...items), options) : suite2(formatTitle(name, items, idx), () => fn(i), options);
- });
- };
- };
- suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
- suiteFn.runIf = (condition) => condition ? suite : suite.skip;
- return createChainable(
- ["concurrent", "shuffle", "skip", "only", "todo"],
- suiteFn
- );
- }
- function createTest(fn) {
- const testFn = fn;
- testFn.each = function(cases, ...args) {
- const test2 = this.withContext();
- if (Array.isArray(cases) && args.length)
- cases = formatTemplateString(cases, args);
- return (name, fn2, options) => {
- const arrayOnlyCases = cases.every(Array.isArray);
- cases.forEach((i, idx) => {
- const items = Array.isArray(i) ? i : [i];
- arrayOnlyCases ? test2(formatTitle(name, items, idx), () => fn2(...items), options) : test2(formatTitle(name, items, idx), () => fn2(i), options);
- });
- };
- };
- testFn.skipIf = (condition) => condition ? test.skip : test;
- testFn.runIf = (condition) => condition ? test : test.skip;
- return createChainable(
- ["concurrent", "skip", "only", "todo", "fails"],
- testFn
- );
- }
- function formatTitle(template, items, idx) {
- if (template.includes("%#")) {
- template = template.replace(/%%/g, "__vitest_escaped_%__").replace(/%#/g, `${idx}`).replace(/__vitest_escaped_%__/g, "%%");
- }
- const count = template.split("%").length - 1;
- let formatted = format(template, ...items.slice(0, count));
- if (isObject(items[0])) {
- formatted = formatted.replace(
- /\$([$\w_.]+)/g,
- (_, key) => objDisplay(objectAttr(items[0], key))
- );
- }
- return formatted;
- }
- function formatTemplateString(cases, args) {
- const header = cases.join("").trim().replace(/ /g, "").split("\n").map((i) => i.split("|"))[0];
- const res = [];
- for (let i = 0; i < Math.floor(args.length / header.length); i++) {
- const oneCase = {};
- for (let j = 0; j < header.length; j++)
- oneCase[header[j]] = args[i * header.length + j];
- res.push(oneCase);
- }
- return res;
- }
-
- async function runSetupFiles(config, runner) {
- const files = toArray(config.setupFiles);
- if (config.sequence.setupFiles === "parallel") {
- await Promise.all(
- files.map(async (fsPath) => {
- await runner.importFile(fsPath, "setup");
- })
- );
- } else {
- for (const fsPath of files)
- await runner.importFile(fsPath, "setup");
- }
- }
-
- const now$1 = Date.now;
- async function collectTests(paths, runner) {
- const files = [];
- const config = runner.config;
- for (const filepath of paths) {
- const path = relative(config.root, filepath);
- const file = {
- id: generateHash(path),
- name: path,
- type: "suite",
- mode: "run",
- filepath,
- tasks: [],
- projectName: config.name
- };
- clearCollectorContext(runner);
- try {
- const setupStart = now$1();
- await runSetupFiles(config, runner);
- const collectStart = now$1();
- file.setupDuration = collectStart - setupStart;
- await runner.importFile(filepath, "collect");
- const defaultTasks = await getDefaultSuite().collect(file);
- setHooks(file, getHooks(defaultTasks));
- for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
- if (c.type === "test") {
- file.tasks.push(c);
- } else if (c.type === "custom") {
- file.tasks.push(c);
- } else if (c.type === "suite") {
- file.tasks.push(c);
- } else if (c.type === "collector") {
- const suite = await c.collect(file);
- if (suite.name || suite.tasks.length)
- file.tasks.push(suite);
- }
- }
- file.collectDuration = now$1() - collectStart;
- } catch (e) {
- const error = processError(e);
- file.result = {
- state: "fail",
- error,
- errors: [error]
- };
- }
- calculateSuiteHash(file);
- const hasOnlyTasks = someTasksAreOnly(file);
- interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly);
- files.push(file);
- }
- return files;
- }
-
- let _test;
- function setCurrentTest(test) {
- _test = test;
- }
- function getCurrentTest() {
- return _test;
- }
-
- const now = Date.now;
- function updateSuiteHookState(suite, name, state, runner) {
- var _a;
- if (!suite.result)
- suite.result = { state: "run" };
- if (!((_a = suite.result) == null ? void 0 : _a.hooks))
- suite.result.hooks = {};
- const suiteHooks = suite.result.hooks;
- if (suiteHooks) {
- suiteHooks[name] = state;
- updateTask(suite, runner);
- }
- }
- function getSuiteHooks(suite, name, sequence) {
- const hooks = getHooks(suite)[name];
- if (sequence === "stack" && (name === "afterAll" || name === "afterEach"))
- return hooks.slice().reverse();
- return hooks;
- }
- async function callSuiteHook(suite, currentTask, name, runner, args) {
- const sequence = runner.config.sequence.hooks;
- const callbacks = [];
- if (name === "beforeEach" && suite.suite) {
- callbacks.push(
- ...await callSuiteHook(suite.suite, currentTask, name, runner, args)
- );
- }
- updateSuiteHookState(currentTask, name, "run", runner);
- const hooks = getSuiteHooks(suite, name, sequence);
- if (sequence === "parallel") {
- callbacks.push(...await Promise.all(hooks.map((fn) => fn(...args))));
- } else {
- for (const hook of hooks)
- callbacks.push(await hook(...args));
- }
- updateSuiteHookState(currentTask, name, "pass", runner);
- if (name === "afterEach" && suite.suite) {
- callbacks.push(
- ...await callSuiteHook(suite.suite, currentTask, name, runner, args)
- );
- }
- return callbacks;
- }
- const packs = /* @__PURE__ */ new Map();
- let updateTimer;
- let previousUpdate;
- function updateTask(task, runner) {
- packs.set(task.id, task.result);
- const { clearTimeout, setTimeout } = getSafeTimers();
- clearTimeout(updateTimer);
- updateTimer = setTimeout(() => {
- previousUpdate = sendTasksUpdate(runner);
- }, 10);
- }
- async function sendTasksUpdate(runner) {
- var _a;
- const { clearTimeout } = getSafeTimers();
- clearTimeout(updateTimer);
- await previousUpdate;
- if (packs.size) {
- const p = (_a = runner.onTaskUpdate) == null ? void 0 : _a.call(runner, Array.from(packs));
- packs.clear();
- return p;
- }
- }
- const callCleanupHooks = async (cleanups) => {
- await Promise.all(cleanups.map(async (fn) => {
- if (typeof fn !== "function")
- return;
- await fn();
- }));
- };
- async function runTest(test, runner) {
- var _a, _b, _c, _d, _e, _f;
- await ((_a = runner.onBeforeRunTest) == null ? void 0 : _a.call(runner, test));
- if (test.mode !== "run")
- return;
- if (((_b = test.result) == null ? void 0 : _b.state) === "fail") {
- updateTask(test, runner);
- return;
- }
- const start = now();
- test.result = {
- state: "run",
- startTime: start
- };
- updateTask(test, runner);
- setCurrentTest(test);
- const retry = test.retry || 1;
- for (let retryCount = 0; retryCount < retry; retryCount++) {
- let beforeEachCleanups = [];
- try {
- await ((_c = runner.onBeforeTryTest) == null ? void 0 : _c.call(runner, test, retryCount));
- beforeEachCleanups = await callSuiteHook(test.suite, test, "beforeEach", runner, [test.context, test.suite]);
- test.result.retryCount = retryCount;
- if (runner.runTest) {
- await runner.runTest(test);
- } else {
- const fn = getFn(test);
- if (!fn)
- throw new Error("Test function is not found. Did you add it using `setFn`?");
- await fn();
- }
- await ((_d = runner.onAfterTryTest) == null ? void 0 : _d.call(runner, test, retryCount));
- test.result.state = "pass";
- } catch (e) {
- failTask(test.result, e);
- }
- try {
- await callSuiteHook(test.suite, test, "afterEach", runner, [test.context, test.suite]);
- await callCleanupHooks(beforeEachCleanups);
- } catch (e) {
- failTask(test.result, e);
- }
- if (test.result.state === "pass")
- break;
- updateTask(test, runner);
- }
- if (test.result.state === "fail")
- await Promise.all(((_e = test.onFailed) == null ? void 0 : _e.map((fn) => fn(test.result))) || []);
- if (test.fails) {
- if (test.result.state === "pass") {
- const error = processError(new Error("Expect test to fail"));
- test.result.state = "fail";
- test.result.error = error;
- test.result.errors = [error];
- } else {
- test.result.state = "pass";
- test.result.error = void 0;
- test.result.errors = void 0;
- }
- }
- setCurrentTest(void 0);
- test.result.duration = now() - start;
- await ((_f = runner.onAfterRunTest) == null ? void 0 : _f.call(runner, test));
- updateTask(test, runner);
- }
- function failTask(result, err) {
- result.state = "fail";
- const error = processError(err);
- result.error = error;
- result.errors ?? (result.errors = []);
- result.errors.push(error);
- }
- function markTasksAsSkipped(suite, runner) {
- suite.tasks.forEach((t) => {
- t.mode = "skip";
- t.result = { ...t.result, state: "skip" };
- updateTask(t, runner);
- if (t.type === "suite")
- markTasksAsSkipped(t, runner);
- });
- }
- async function runSuite(suite, runner) {
- var _a, _b, _c;
- await ((_a = runner.onBeforeRunSuite) == null ? void 0 : _a.call(runner, suite));
- if (((_b = suite.result) == null ? void 0 : _b.state) === "fail") {
- markTasksAsSkipped(suite, runner);
- updateTask(suite, runner);
- return;
- }
- const start = now();
- suite.result = {
- state: "run",
- startTime: start
- };
- updateTask(suite, runner);
- let beforeAllCleanups = [];
- if (suite.mode === "skip") {
- suite.result.state = "skip";
- } else if (suite.mode === "todo") {
- suite.result.state = "todo";
- } else {
- try {
- beforeAllCleanups = await callSuiteHook(suite, suite, "beforeAll", runner, [suite]);
- if (runner.runSuite) {
- await runner.runSuite(suite);
- } else {
- for (let tasksGroup of partitionSuiteChildren(suite)) {
- if (tasksGroup[0].concurrent === true) {
- const mutex = limit(runner.config.maxConcurrency);
- await Promise.all(tasksGroup.map((c) => mutex(() => runSuiteChild(c, runner))));
- } else {
- const { sequence } = runner.config;
- if (sequence.shuffle || suite.shuffle) {
- const suites = tasksGroup.filter((group) => group.type === "suite");
- const tests = tasksGroup.filter((group) => group.type === "test");
- const groups = shuffle([suites, tests], sequence.seed);
- tasksGroup = groups.flatMap((group) => shuffle(group, sequence.seed));
- }
- for (const c of tasksGroup)
- await runSuiteChild(c, runner);
- }
- }
- }
- } catch (e) {
- failTask(suite.result, e);
- }
- try {
- await callSuiteHook(suite, suite, "afterAll", runner, [suite]);
- await callCleanupHooks(beforeAllCleanups);
- } catch (e) {
- failTask(suite.result, e);
- }
- }
- suite.result.duration = now() - start;
- if (suite.mode === "run") {
- if (!hasTests(suite)) {
- suite.result.state = "fail";
- if (!suite.result.error) {
- const error = processError(new Error(`No test found in suite ${suite.name}`));
- suite.result.error = error;
- suite.result.errors = [error];
- }
- } else if (hasFailed(suite)) {
- suite.result.state = "fail";
- } else {
- suite.result.state = "pass";
- }
- }
- await ((_c = runner.onAfterRunSuite) == null ? void 0 : _c.call(runner, suite));
- updateTask(suite, runner);
- }
- async function runSuiteChild(c, runner) {
- if (c.type === "test")
- return runTest(c, runner);
- else if (c.type === "suite")
- return runSuite(c, runner);
- }
- async function runFiles(files, runner) {
- var _a, _b;
- for (const file of files) {
- if (!file.tasks.length && !runner.config.passWithNoTests) {
- if (!((_b = (_a = file.result) == null ? void 0 : _a.errors) == null ? void 0 : _b.length)) {
- const error = processError(new Error(`No test suite found in file ${file.filepath}`));
- file.result = {
- state: "fail",
- error,
- errors: [error]
- };
- }
- }
- await runSuite(file, runner);
- }
- }
- async function startTests(paths, runner) {
- var _a, _b, _c, _d;
- await ((_a = runner.onBeforeCollect) == null ? void 0 : _a.call(runner, paths));
- const files = await collectTests(paths, runner);
- (_b = runner.onCollected) == null ? void 0 : _b.call(runner, files);
- await ((_c = runner.onBeforeRun) == null ? void 0 : _c.call(runner, files));
- await runFiles(files, runner);
- await ((_d = runner.onAfterRun) == null ? void 0 : _d.call(runner, files));
- await sendTasksUpdate(runner);
- return files;
- }
-
- const getDefaultHookTimeout = () => getRunner().config.hookTimeout;
- const beforeAll = (fn, timeout) => getCurrentSuite().on("beforeAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
- const afterAll = (fn, timeout) => getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
- const beforeEach = (fn, timeout) => getCurrentSuite().on("beforeEach", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
- const afterEach = (fn, timeout) => getCurrentSuite().on("afterEach", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
- const onTestFailed = createTestHook("onTestFailed", (test, handler) => {
- test.onFailed || (test.onFailed = []);
- test.onFailed.push(handler);
- });
- function createTestHook(name, handler) {
- return (fn) => {
- const current = getCurrentTest();
- if (!current)
- throw new Error(`Hook ${name}() can only be called inside a test`);
- handler(current, fn);
- };
- }
-
- export { afterAll, afterEach, beforeAll, beforeEach, describe, getCurrentSuite, getFn, it, onTestFailed, setFn, startTests, suite, test, updateTask };
|