dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study

- New /developers/ page with API docs, SDKs, pricing, use cases
- OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics
- Case study: Glasskiosken i Flatenbadet — complete ROI analysis
- Updated /order/ with recurring missions and frequency dropdown
This commit is contained in:
Bernt
2026-07-14 15:27:33 +00:00
parent 3c522e39f0
commit 1a12fb870b
6296 changed files with 911440 additions and 55607 deletions
+171
View File
@@ -0,0 +1,171 @@
import { processError } from '@vitest/utils/error';
import { toArray } from '@vitest/utils';
function partitionSuiteChildren(suite) {
let tasksGroup = [];
const tasksGroups = [];
for (const c of suite.tasks) {
if (tasksGroup.length === 0 || c.concurrent === tasksGroup[0].concurrent) {
tasksGroup.push(c);
} else {
tasksGroups.push(tasksGroup);
tasksGroup = [c];
}
}
if (tasksGroup.length > 0)
tasksGroups.push(tasksGroup);
return tasksGroups;
}
function interpretTaskModes(suite, namePattern, onlyMode, parentIsOnly, allowOnly) {
const suiteIsOnly = parentIsOnly || suite.mode === "only";
suite.tasks.forEach((t) => {
const includeTask = suiteIsOnly || t.mode === "only";
if (onlyMode) {
if (t.type === "suite" && (includeTask || someTasksAreOnly(t))) {
if (t.mode === "only") {
checkAllowOnly(t, allowOnly);
t.mode = "run";
}
} else if (t.mode === "run" && !includeTask) {
t.mode = "skip";
} else if (t.mode === "only") {
checkAllowOnly(t, allowOnly);
t.mode = "run";
}
}
if (t.type === "test") {
if (namePattern && !getTaskFullName(t).match(namePattern))
t.mode = "skip";
} else if (t.type === "suite") {
if (t.mode === "skip")
skipAllTasks(t);
else
interpretTaskModes(t, namePattern, onlyMode, includeTask, allowOnly);
}
});
if (suite.mode === "run") {
if (suite.tasks.length && suite.tasks.every((i) => i.mode !== "run"))
suite.mode = "skip";
}
}
function getTaskFullName(task) {
return `${task.suite ? `${getTaskFullName(task.suite)} ` : ""}${task.name}`;
}
function someTasksAreOnly(suite) {
return suite.tasks.some((t) => t.mode === "only" || t.type === "suite" && someTasksAreOnly(t));
}
function skipAllTasks(suite) {
suite.tasks.forEach((t) => {
if (t.mode === "run") {
t.mode = "skip";
if (t.type === "suite")
skipAllTasks(t);
}
});
}
function checkAllowOnly(task, allowOnly) {
if (allowOnly)
return;
const error = processError(new Error("[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error"));
task.result = {
state: "fail",
errors: [error]
};
}
function generateHash(str) {
let hash = 0;
if (str.length === 0)
return `${hash}`;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return `${hash}`;
}
function calculateSuiteHash(parent) {
parent.tasks.forEach((t, idx) => {
t.id = `${parent.id}_${idx}`;
if (t.type === "suite")
calculateSuiteHash(t);
});
}
function createChainable(keys, fn) {
function create(context) {
const chain2 = function(...args) {
return fn.apply(context, args);
};
Object.assign(chain2, fn);
chain2.withContext = () => chain2.bind(context);
chain2.setContext = (key, value) => {
context[key] = value;
};
chain2.mergeContext = (ctx) => {
Object.assign(context, ctx);
};
for (const key of keys) {
Object.defineProperty(chain2, key, {
get() {
return create({ ...context, [key]: true });
}
});
}
return chain2;
}
const chain = create({});
chain.fn = fn;
return chain;
}
function isAtomTest(s) {
return s.type === "test" || s.type === "custom";
}
function getTests(suite) {
const tests = [];
const arraySuites = toArray(suite);
for (const s of arraySuites) {
if (isAtomTest(s)) {
tests.push(s);
} else {
for (const task of s.tasks) {
if (isAtomTest(task)) {
tests.push(task);
} else {
const taskTests = getTests(task);
for (const test of taskTests)
tests.push(test);
}
}
}
}
return tests;
}
function getTasks(tasks = []) {
return toArray(tasks).flatMap((s) => isAtomTest(s) ? [s] : [s, ...getTasks(s.tasks)]);
}
function getSuites(suite) {
return toArray(suite).flatMap((s) => s.type === "suite" ? [s, ...getSuites(s.tasks)] : []);
}
function hasTests(suite) {
return toArray(suite).some((s) => s.tasks.some((c) => isAtomTest(c) || hasTests(c)));
}
function hasFailed(suite) {
return toArray(suite).some((s) => {
var _a;
return ((_a = s.result) == null ? void 0 : _a.state) === "fail" || s.type === "suite" && hasFailed(s.tasks);
});
}
function getNames(task) {
const names = [task.name];
let current = task;
while ((current == null ? void 0 : current.suite) || (current == null ? void 0 : current.file)) {
current = current.suite || current.file;
if (current == null ? void 0 : current.name)
names.unshift(current.name);
}
return names;
}
export { getTests as a, getTasks as b, calculateSuiteHash as c, getSuites as d, hasFailed as e, getNames as f, generateHash as g, hasTests as h, interpretTaskModes as i, createChainable as j, partitionSuiteChildren as p, someTasksAreOnly as s };
+33
View File
@@ -0,0 +1,33 @@
import { VitestRunner } from './types.js';
export { CancelReason, VitestRunnerConfig, VitestRunnerConstructor, VitestRunnerImportSource } from './types.js';
import { T as Task, F as File, d as SuiteAPI, e as TestAPI, f as SuiteCollector, g as CustomAPI, h as SuiteHooks, O as OnTestFailedHandler, i as OnTestFinishedHandler, a as Test, C as Custom, S as Suite } from './tasks-K5XERDtv.js';
export { D as DoneCallback, E as ExtendedContext, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, R as RunMode, y as RuntimeContext, B as SequenceHooks, G as SequenceSetupFiles, x as SuiteFactory, k as TaskBase, A as TaskContext, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, o as TaskResultPack, j as TaskState, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-K5XERDtv.js';
import { Awaitable } from '@vitest/utils';
export { processError } from '@vitest/utils/error';
import '@vitest/utils/diff';
declare function updateTask(task: Task, runner: VitestRunner): void;
declare function startTests(paths: string[], runner: VitestRunner): Promise<File[]>;
declare const suite: SuiteAPI;
declare const test: TestAPI;
declare const describe: SuiteAPI;
declare const it: TestAPI;
declare function getCurrentSuite<ExtraContext = {}>(): SuiteCollector<ExtraContext>;
declare function createTaskCollector(fn: (...args: any[]) => any, context?: Record<string, unknown>): CustomAPI;
declare function beforeAll(fn: SuiteHooks['beforeAll'][0], timeout?: number): void;
declare function afterAll(fn: SuiteHooks['afterAll'][0], timeout?: number): void;
declare function beforeEach<ExtraContext = {}>(fn: SuiteHooks<ExtraContext>['beforeEach'][0], timeout?: number): void;
declare function afterEach<ExtraContext = {}>(fn: SuiteHooks<ExtraContext>['afterEach'][0], timeout?: number): void;
declare const onTestFailed: (fn: OnTestFailedHandler) => void;
declare const onTestFinished: (fn: OnTestFinishedHandler) => void;
declare function setFn(key: Test | Custom, fn: (() => Awaitable<void>)): void;
declare function getFn<Task = Test | Custom>(key: Task): (() => Awaitable<void>);
declare function setHooks(key: Suite, hooks: SuiteHooks): void;
declare function getHooks(key: Suite): SuiteHooks;
declare function getCurrentTest<T extends Test | Custom | undefined>(): T;
export { Custom, CustomAPI, File, OnTestFailedHandler, OnTestFinishedHandler, Suite, SuiteAPI, SuiteCollector, SuiteHooks, Task, Test, TestAPI, VitestRunner, afterAll, afterEach, beforeAll, beforeEach, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, setFn, setHooks, startTests, suite, test, updateTask };
+1005
View File
@@ -0,0 +1,1005 @@
import limit from 'p-limit';
import { getSafeTimers, isObject, createDefer, format, objDisplay, objectAttr, toArray, shuffle } from '@vitest/utils';
import { processError } from '@vitest/utils/error';
export { processError } from '@vitest/utils/error';
import { j as createChainable, g as generateHash, c as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, p as partitionSuiteChildren, h as hasTests, e as hasFailed } from './chunk-tasks.js';
import { relative } from 'pathe';
import { parseSingleStack } from '@vitest/utils/source-map';
const fnMap = /* @__PURE__ */ new WeakMap();
const fixtureMap = /* @__PURE__ */ new WeakMap();
const hooksMap = /* @__PURE__ */ new WeakMap();
function setFn(key, fn) {
fnMap.set(key, fn);
}
function getFn(key) {
return fnMap.get(key);
}
function setFixture(key, fixture) {
fixtureMap.set(key, fixture);
}
function getFixture(key) {
return fixtureMap.get(key);
}
function setHooks(key, hooks) {
hooksMap.set(key, hooks);
}
function getHooks(key) {
return hooksMap.get(key);
}
class PendingError extends Error {
constructor(message, task) {
super(message);
this.message = message;
this.taskId = task.id;
}
code = "VITEST_PENDING";
taskId;
}
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 === Number.POSITIVE_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.task = test;
context.skip = () => {
test.pending = true;
throw new PendingError("test is skipped; abort execution", test);
};
context.onTestFailed = (fn) => {
test.onFailed || (test.onFailed = []);
test.onFailed.push(fn);
};
context.onTestFinished = (fn) => {
test.onFinished || (test.onFinished = []);
test.onFinished.push(fn);
};
return ((_a = runner.extendTaskContext) == 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"}".`;
}
function mergeContextFixtures(fixtures, context = {}) {
const fixtureOptionKeys = ["auto"];
const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
const fixtureItem = { value };
if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
Object.assign(fixtureItem, value[1]);
fixtureItem.value = value[0];
}
fixtureItem.prop = prop;
fixtureItem.isFn = typeof fixtureItem.value === "function";
return fixtureItem;
});
if (Array.isArray(context.fixtures))
context.fixtures = context.fixtures.concat(fixtureArray);
else
context.fixtures = fixtureArray;
fixtureArray.forEach((fixture) => {
if (fixture.isFn) {
const usedProps = getUsedProps(fixture.value);
if (usedProps.length)
fixture.deps = context.fixtures.filter(({ prop }) => prop !== fixture.prop && usedProps.includes(prop));
}
});
return context;
}
const fixtureValueMaps = /* @__PURE__ */ new Map();
const cleanupFnArrayMap = /* @__PURE__ */ new Map();
async function callFixtureCleanup(context) {
const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
for (const cleanup of cleanupFnArray.reverse())
await cleanup();
cleanupFnArrayMap.delete(context);
}
function withFixtures(fn, testContext) {
return (hookContext) => {
const context = hookContext || testContext;
if (!context)
return fn({});
const fixtures = getFixture(context);
if (!(fixtures == null ? void 0 : fixtures.length))
return fn(context);
const usedProps = getUsedProps(fn);
const hasAutoFixture = fixtures.some(({ auto }) => auto);
if (!usedProps.length && !hasAutoFixture)
return fn(context);
if (!fixtureValueMaps.get(context))
fixtureValueMaps.set(context, /* @__PURE__ */ new Map());
const fixtureValueMap = fixtureValueMaps.get(context);
if (!cleanupFnArrayMap.has(context))
cleanupFnArrayMap.set(context, []);
const cleanupFnArray = cleanupFnArrayMap.get(context);
const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop));
const pendingFixtures = resolveDeps(usedFixtures);
if (!pendingFixtures.length)
return fn(context);
async function resolveFixtures() {
for (const fixture of pendingFixtures) {
if (fixtureValueMap.has(fixture))
continue;
const resolvedValue = fixture.isFn ? await resolveFixtureFunction(fixture.value, context, cleanupFnArray) : fixture.value;
context[fixture.prop] = resolvedValue;
fixtureValueMap.set(fixture, resolvedValue);
cleanupFnArray.unshift(() => {
fixtureValueMap.delete(fixture);
});
}
}
return resolveFixtures().then(() => fn(context));
};
}
async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
const useFnArgPromise = createDefer();
let isUseFnArgResolved = false;
const fixtureReturn = fixtureFn(context, async (useFnArg) => {
isUseFnArgResolved = true;
useFnArgPromise.resolve(useFnArg);
const useReturnPromise = createDefer();
cleanupFnArray.push(async () => {
useReturnPromise.resolve();
await fixtureReturn;
});
await useReturnPromise;
}).catch((e) => {
if (!isUseFnArgResolved) {
useFnArgPromise.reject(e);
return;
}
throw e;
});
return useFnArgPromise;
}
function resolveDeps(fixtures, depSet = /* @__PURE__ */ new Set(), pendingFixtures = []) {
fixtures.forEach((fixture) => {
if (pendingFixtures.includes(fixture))
return;
if (!fixture.isFn || !fixture.deps) {
pendingFixtures.push(fixture);
return;
}
if (depSet.has(fixture))
throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`);
depSet.add(fixture);
resolveDeps(fixture.deps, depSet, pendingFixtures);
pendingFixtures.push(fixture);
depSet.clear();
});
return pendingFixtures;
}
function getUsedProps(fn) {
const match = fn.toString().match(/[^(]*\(([^)]*)/);
if (!match)
return [];
const args = splitByComma(match[1]);
if (!args.length)
return [];
const first = args[0];
if (!(first.startsWith("{") && first.endsWith("}")))
throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
const _first = first.slice(1, -1).replace(/\s/g, "");
const props = splitByComma(_first).map((prop) => {
return prop.replace(/\:.*|\=.*/g, "");
});
const last = props.at(-1);
if (last && last.startsWith("..."))
throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
return props;
}
function splitByComma(s) {
const result = [];
const stack = [];
let start = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === "{" || s[i] === "[") {
stack.push(s[i] === "{" ? "}" : "]");
} else if (s[i] === stack[stack.length - 1]) {
stack.pop();
} else if (!stack.length && s[i] === ",") {
const token = s.substring(start, i).trim();
if (token)
result.push(token);
start = i + 1;
}
}
const lastToken = s.substring(start).trim();
if (lastToken)
result.push(lastToken);
return result;
}
let _test;
function setCurrentTest(test) {
_test = test;
}
function getCurrentTest() {
return _test;
}
const suite = createSuite();
const test = createTest(
function(name, optionsOrFn, optionsOrTest) {
if (getCurrentTest())
throw new Error('Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.');
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
}
);
const describe = suite;
const it = test;
let runner;
let defaultSuite;
let currentTestFilepath;
function getDefaultSuite() {
return defaultSuite;
}
function getTestFilepath() {
return currentTestFilepath;
}
function getRunner() {
return runner;
}
function clearCollectorContext(filepath, currentRunner) {
if (!defaultSuite)
defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle("") : currentRunner.config.sequence.concurrent ? suite.concurrent("") : suite("");
runner = currentRunner;
currentTestFilepath = filepath;
collectorContext.tasks.length = 0;
defaultSuite.clear();
collectorContext.currentSuite = defaultSuite;
}
function getCurrentSuite() {
return collectorContext.currentSuite || defaultSuite;
}
function createSuiteHooks() {
return {
beforeAll: [],
afterAll: [],
beforeEach: [],
afterEach: []
};
}
function parseArguments(optionsOrFn, optionsOrTest) {
let options = {};
let fn = () => {
};
if (typeof optionsOrTest === "object") {
if (typeof optionsOrFn === "object")
throw new TypeError("Cannot use two objects as arguments. Please provide options and a function callback in that order.");
options = optionsOrTest;
} else if (typeof optionsOrTest === "number") {
options = { timeout: optionsOrTest };
} else if (typeof optionsOrFn === "object") {
options = optionsOrFn;
}
if (typeof optionsOrFn === "function") {
if (typeof optionsOrTest === "function")
throw new TypeError("Cannot use two functions as arguments. Please use the second argument for options.");
fn = optionsOrFn;
} else if (typeof optionsOrTest === "function") {
fn = optionsOrTest;
}
return {
options,
handler: fn
};
}
function createSuiteCollector(name, factory = () => {
}, mode, shuffle, each, suiteOptions) {
const tasks = [];
const factoryQueue = [];
let suite2;
initSuite(true);
const task = function(name2 = "", options = {}) {
const task2 = {
id: "",
name: name2,
suite: void 0,
each: options.each,
fails: options.fails,
context: void 0,
type: "custom",
retry: options.retry ?? runner.config.retry,
repeats: options.repeats,
mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
meta: options.meta ?? /* @__PURE__ */ Object.create(null)
};
const handler = options.handler;
if (options.concurrent || !options.sequential && runner.config.sequence.concurrent)
task2.concurrent = true;
if (shuffle)
task2.shuffle = true;
const context = createTestContext(task2, runner);
Object.defineProperty(task2, "context", {
value: context,
enumerable: false
});
setFixture(context, options.fixtures);
if (handler) {
setFn(task2, withTimeout(
withFixtures(handler, context),
(options == null ? void 0 : options.timeout) ?? runner.config.testTimeout
));
}
if (runner.config.includeTaskLocation) {
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 15;
const error = new Error("stacktrace").stack;
Error.stackTraceLimit = limit;
const stack = findTestFileStackTrace(error, task2.each ?? false);
if (stack)
task2.location = stack;
}
tasks.push(task2);
return task2;
};
const test2 = createTest(function(name2, optionsOrFn, optionsOrTest) {
let { options, handler } = parseArguments(
optionsOrFn,
optionsOrTest
);
if (typeof suiteOptions === "object")
options = Object.assign({}, suiteOptions, options);
options.concurrent = this.concurrent || !this.sequential && (options == null ? void 0 : options.concurrent);
options.sequential = this.sequential || !this.concurrent && (options == null ? void 0 : options.sequential);
const test3 = task(
formatName(name2),
{ ...this, ...options, handler }
);
test3.type = "test";
});
const collector = {
type: "collector",
name,
mode,
options: suiteOptions,
test: test2,
tasks,
collect,
task,
clear,
on: addHook
};
function addHook(name2, ...fn) {
getHooks(suite2)[name2].push(...fn);
}
function initSuite(includeLocation) {
if (typeof suiteOptions === "number")
suiteOptions = { timeout: suiteOptions };
suite2 = {
id: "",
type: "suite",
name,
mode,
each,
shuffle,
tasks: [],
meta: /* @__PURE__ */ Object.create(null),
projectName: ""
};
if (runner && includeLocation && runner.config.includeTaskLocation) {
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 15;
const error = new Error("stacktrace").stack;
Error.stackTraceLimit = limit;
const stack = findTestFileStackTrace(error, suite2.each ?? false);
if (stack)
suite2.location = stack;
}
setHooks(suite2, createSuiteHooks());
}
function clear() {
tasks.length = 0;
factoryQueue.length = 0;
initSuite(false);
}
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((task2) => {
task2.suite = suite2;
if (file)
task2.file = file;
});
return suite2;
}
collectTask(collector);
return collector;
}
function createSuite() {
function suiteFn(name, factoryOrOptions, optionsOrFactory = {}) {
const mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
const currentSuite = getCurrentSuite();
let { options, handler: factory } = parseArguments(
factoryOrOptions,
optionsOrFactory
);
if (currentSuite == null ? void 0 : currentSuite.options)
options = { ...currentSuite.options, ...options };
options.concurrent = this.concurrent || !this.sequential && (options == null ? void 0 : options.concurrent);
options.sequential = this.sequential || !this.concurrent && (options == null ? void 0 : options.sequential);
return createSuiteCollector(formatName(name), factory, mode, this.shuffle, this.each, options);
}
suiteFn.each = function(cases, ...args) {
const suite2 = this.withContext();
this.setContext("each", true);
if (Array.isArray(cases) && args.length)
cases = formatTemplateString(cases, args);
return (name, optionsOrFn, fnOrOptions) => {
const _name = formatName(name);
const arrayOnlyCases = cases.every(Array.isArray);
const { options, handler } = parseArguments(
optionsOrFn,
fnOrOptions
);
const fnFirst = typeof optionsOrFn === "function";
cases.forEach((i, idx) => {
const items = Array.isArray(i) ? i : [i];
if (fnFirst) {
arrayOnlyCases ? suite2(formatTitle(_name, items, idx), () => handler(...items), options) : suite2(formatTitle(_name, items, idx), () => handler(i), options);
} else {
arrayOnlyCases ? suite2(formatTitle(_name, items, idx), options, () => handler(...items)) : suite2(formatTitle(_name, items, idx), options, () => handler(i));
}
});
this.setContext("each", void 0);
};
};
suiteFn.skipIf = (condition) => condition ? suite.skip : suite;
suiteFn.runIf = (condition) => condition ? suite : suite.skip;
return createChainable(
["concurrent", "sequential", "shuffle", "skip", "only", "todo"],
suiteFn
);
}
function createTaskCollector(fn, context) {
const taskFn = fn;
taskFn.each = function(cases, ...args) {
const test2 = this.withContext();
this.setContext("each", true);
if (Array.isArray(cases) && args.length)
cases = formatTemplateString(cases, args);
return (name, optionsOrFn, fnOrOptions) => {
const _name = formatName(name);
const arrayOnlyCases = cases.every(Array.isArray);
const { options, handler } = parseArguments(
optionsOrFn,
fnOrOptions
);
const fnFirst = typeof optionsOrFn === "function";
cases.forEach((i, idx) => {
const items = Array.isArray(i) ? i : [i];
if (fnFirst) {
arrayOnlyCases ? test2(formatTitle(_name, items, idx), () => handler(...items), options) : test2(formatTitle(_name, items, idx), () => handler(i), options);
} else {
arrayOnlyCases ? test2(formatTitle(_name, items, idx), options, () => handler(...items)) : test2(formatTitle(_name, items, idx), options, () => handler(i));
}
});
this.setContext("each", void 0);
};
};
taskFn.skipIf = function(condition) {
return condition ? this.skip : this;
};
taskFn.runIf = function(condition) {
return condition ? this : this.skip;
};
taskFn.extend = function(fixtures) {
const _context = mergeContextFixtures(fixtures, context);
return createTest(function fn2(name, optionsOrFn, optionsOrTest) {
getCurrentSuite().test.fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
}, _context);
};
const _test = createChainable(
["concurrent", "sequential", "skip", "only", "todo", "fails"],
taskFn
);
if (context)
_test.mergeContext(context);
return _test;
}
function createTest(fn, context) {
return createTaskCollector(fn, context);
}
function formatName(name) {
return typeof name === "string" ? name : name instanceof Function ? name.name || "<anonymous>" : String(name);
}
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,
// https://github.com/chaijs/chai/pull/1490
(_, key) => {
var _a, _b;
return objDisplay(objectAttr(items[0], key), { truncate: (_b = (_a = runner == null ? void 0 : runner.config) == null ? void 0 : _a.chaiConfig) == null ? void 0 : _b.truncateThreshold });
}
);
}
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;
}
function findTestFileStackTrace(error, each) {
const lines = error.split("\n").slice(1);
for (const line of lines) {
const stack = parseSingleStack(line);
if (stack && stack.file === getTestFilepath()) {
return {
line: stack.line,
/**
* test.each([1, 2])('name')
* ^ leads here, but should
* ^ lead here
* in source maps it's the same boundary, so it just points to the start of it
*/
column: each ? stack.column + 1 : stack.column
};
}
}
}
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}${config.name || ""}`),
name: path,
type: "suite",
mode: "run",
filepath,
tasks: [],
meta: /* @__PURE__ */ Object.create(null),
projectName: config.name
};
clearCollectorContext(filepath, 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",
errors: [error]
};
}
calculateSuiteHash(file);
const hasOnlyTasks = someTasksAreOnly(file);
interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly);
files.push(file);
}
return files;
}
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 callTaskHooks(task, hooks, sequence) {
if (sequence === "stack")
hooks = hooks.slice().reverse();
if (sequence === "parallel") {
await Promise.all(hooks.map((fn) => fn(task.result)));
} else {
for (const fn of hooks)
await fn(task.result);
}
}
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, task.meta]);
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 taskPacks = Array.from(packs).map(([id, task]) => {
return [
id,
task[0],
task[1]
];
});
const p = (_a = runner.onTaskUpdate) == null ? void 0 : _a.call(runner, taskPacks);
packs.clear();
return p;
}
}
async function callCleanupHooks(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.onBeforeRunTask) == 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,
retryCount: 0
};
updateTask(test, runner);
setCurrentTest(test);
const repeats = test.repeats ?? 0;
for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
const retry = test.retry ?? 0;
for (let retryCount = 0; retryCount <= retry; retryCount++) {
let beforeEachCleanups = [];
try {
await ((_c = runner.onBeforeTryTask) == null ? void 0 : _c.call(runner, test, { retry: retryCount, repeats: repeatCount }));
test.result.repeatCount = repeatCount;
beforeEachCleanups = await callSuiteHook(test.suite, test, "beforeEach", runner, [test.context, test.suite]);
if (runner.runTask) {
await runner.runTask(test);
} else {
const fn = getFn(test);
if (!fn)
throw new Error("Test function is not found. Did you add it using `setFn`?");
await fn();
}
if (test.promises) {
const result = await Promise.allSettled(test.promises);
const errors = result.map((r) => r.status === "rejected" ? r.reason : void 0).filter(Boolean);
if (errors.length)
throw errors;
}
await ((_d = runner.onAfterTryTask) == null ? void 0 : _d.call(runner, test, { retry: retryCount, repeats: repeatCount }));
if (test.result.state !== "fail") {
if (!test.repeats)
test.result.state = "pass";
else if (test.repeats && retry === retryCount)
test.result.state = "pass";
}
} catch (e) {
failTask(test.result, e, runner.config.diffOptions);
}
if (test.pending || ((_e = test.result) == null ? void 0 : _e.state) === "skip") {
test.mode = "skip";
test.result = { state: "skip" };
updateTask(test, runner);
setCurrentTest(void 0);
return;
}
try {
await callSuiteHook(test.suite, test, "afterEach", runner, [test.context, test.suite]);
await callCleanupHooks(beforeEachCleanups);
await callFixtureCleanup(test.context);
} catch (e) {
failTask(test.result, e, runner.config.diffOptions);
}
if (test.result.state === "pass")
break;
if (retryCount < retry) {
test.result.state = "run";
test.result.retryCount = (test.result.retryCount ?? 0) + 1;
}
updateTask(test, runner);
}
}
try {
await callTaskHooks(test, test.onFinished || [], "stack");
} catch (e) {
failTask(test.result, e, runner.config.diffOptions);
}
if (test.result.state === "fail") {
try {
await callTaskHooks(test, test.onFailed || [], runner.config.sequence.hooks);
} catch (e) {
failTask(test.result, e, runner.config.diffOptions);
}
}
if (test.fails) {
if (test.result.state === "pass") {
const error = processError(new Error("Expect test to fail"));
test.result.state = "fail";
test.result.errors = [error];
} else {
test.result.state = "pass";
test.result.errors = void 0;
}
}
setCurrentTest(void 0);
test.result.duration = now() - start;
await ((_f = runner.onAfterRunTask) == null ? void 0 : _f.call(runner, test));
updateTask(test, runner);
}
function failTask(result, err, diffOptions) {
if (err instanceof PendingError) {
result.state = "skip";
return;
}
result.state = "fail";
const errors = Array.isArray(err) ? err : [err];
for (const e of errors) {
const error = processError(e, diffOptions);
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, _d;
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, runner.config.diffOptions);
}
try {
await callSuiteHook(suite, suite, "afterAll", runner, [suite]);
await callCleanupHooks(beforeAllCleanups);
} catch (e) {
failTask(suite.result, e, runner.config.diffOptions);
}
if (suite.mode === "run") {
if (!runner.config.passWithNoTests && !hasTests(suite)) {
suite.result.state = "fail";
if (!((_c = suite.result.errors) == null ? void 0 : _c.length)) {
const error = processError(new Error(`No test found in suite ${suite.name}`));
suite.result.errors = [error];
}
} else if (hasFailed(suite)) {
suite.result.state = "fail";
} else {
suite.result.state = "pass";
}
}
updateTask(suite, runner);
suite.result.duration = now() - start;
await ((_d = runner.onAfterRunSuite) == null ? void 0 : _d.call(runner, suite));
}
}
async function runSuiteChild(c, runner) {
if (c.type === "test" || c.type === "custom")
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",
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);
await ((_b = runner.onCollected) == null ? void 0 : _b.call(runner, files));
await ((_c = runner.onBeforeRunFiles) == null ? void 0 : _c.call(runner, files));
await runFiles(files, runner);
await ((_d = runner.onAfterRunFiles) == null ? void 0 : _d.call(runner, files));
await sendTasksUpdate(runner);
return files;
}
function getDefaultHookTimeout() {
return getRunner().config.hookTimeout;
}
function beforeAll(fn, timeout) {
return getCurrentSuite().on("beforeAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
}
function afterAll(fn, timeout) {
return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true));
}
function beforeEach(fn, timeout) {
return getCurrentSuite().on("beforeEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true));
}
function afterEach(fn, timeout) {
return getCurrentSuite().on("afterEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true));
}
const onTestFailed = createTestHook("onTestFailed", (test, handler) => {
test.onFailed || (test.onFailed = []);
test.onFailed.push(handler);
});
const onTestFinished = createTestHook("onTestFinished", (test, handler) => {
test.onFinished || (test.onFinished = []);
test.onFinished.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`);
return handler(current, fn);
};
}
export { afterAll, afterEach, beforeAll, beforeEach, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, setFn, setHooks, startTests, suite, test, updateTask };
@@ -0,0 +1,280 @@
import { ErrorWithDiff, Awaitable } from '@vitest/utils';
type ChainableFunction<T extends string, F extends (...args: any) => any, C = {}> = F & {
[x in T]: ChainableFunction<T, F, C>;
} & {
fn: (this: Record<T, any>, ...args: Parameters<F>) => ReturnType<F>;
} & C;
declare function createChainable<T extends string, Args extends any[], R = any>(keys: T[], fn: (this: Record<T, any>, ...args: Args) => R): ChainableFunction<T, (...args: Args) => R>;
interface FixtureItem extends FixtureOptions {
prop: string;
value: any;
/**
* Indicates whether the fixture is a function
*/
isFn: boolean;
/**
* The dependencies(fixtures) of current fixture function.
*/
deps?: FixtureItem[];
}
type RunMode = 'run' | 'skip' | 'only' | 'todo';
type TaskState = RunMode | 'pass' | 'fail';
interface TaskBase {
id: string;
name: string;
mode: RunMode;
meta: TaskMeta;
each?: boolean;
concurrent?: boolean;
shuffle?: boolean;
suite?: Suite;
file?: File;
result?: TaskResult;
retry?: number;
repeats?: number;
location?: {
line: number;
column: number;
};
}
interface TaskPopulated extends TaskBase {
suite: Suite;
pending?: boolean;
result?: TaskResult;
fails?: boolean;
onFailed?: OnTestFailedHandler[];
onFinished?: OnTestFinishedHandler[];
/**
* Store promises (from async expects) to wait for them before finishing the test
*/
promises?: Promise<any>[];
}
interface TaskMeta {
}
interface TaskResult {
state: TaskState;
duration?: number;
startTime?: number;
heap?: number;
errors?: ErrorWithDiff[];
htmlError?: string;
hooks?: Partial<Record<keyof SuiteHooks, TaskState>>;
retryCount?: number;
repeatCount?: number;
}
type TaskResultPack = [id: string, result: TaskResult | undefined, meta: TaskMeta];
interface Suite extends TaskBase {
type: 'suite';
tasks: Task[];
filepath?: string;
projectName: string;
}
interface File extends Suite {
filepath: string;
collectDuration?: number;
setupDuration?: number;
}
interface Test<ExtraContext = {}> extends TaskPopulated {
type: 'test';
context: TaskContext<Test> & ExtraContext & TestContext;
}
interface Custom<ExtraContext = {}> extends TaskPopulated {
type: 'custom';
context: TaskContext<Custom> & ExtraContext & TestContext;
}
type Task = Test | Suite | Custom | File;
type DoneCallback = (error?: any) => void;
type TestFunction<ExtraContext = {}> = (context: ExtendedContext<Test> & ExtraContext) => Awaitable<any> | void;
type ExtractEachCallbackArgs<T extends ReadonlyArray<any>> = {
1: [T[0]];
2: [T[0], T[1]];
3: [T[0], T[1], T[2]];
4: [T[0], T[1], T[2], T[3]];
5: [T[0], T[1], T[2], T[3], T[4]];
6: [T[0], T[1], T[2], T[3], T[4], T[5]];
7: [T[0], T[1], T[2], T[3], T[4], T[5], T[6]];
8: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7]];
9: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8]];
10: [T[0], T[1], T[2], T[3], T[4], T[5], T[6], T[7], T[8], T[9]];
fallback: Array<T extends ReadonlyArray<infer U> ? U : any>;
}[T extends Readonly<[any]> ? 1 : T extends Readonly<[any, any]> ? 2 : T extends Readonly<[any, any, any]> ? 3 : T extends Readonly<[any, any, any, any]> ? 4 : T extends Readonly<[any, any, any, any, any]> ? 5 : T extends Readonly<[any, any, any, any, any, any]> ? 6 : T extends Readonly<[any, any, any, any, any, any, any]> ? 7 : T extends Readonly<[any, any, any, any, any, any, any, any]> ? 8 : T extends Readonly<[any, any, any, any, any, any, any, any, any]> ? 9 : T extends Readonly<[any, any, any, any, any, any, any, any, any, any]> ? 10 : 'fallback'];
interface EachFunctionReturn<T extends any[]> {
/**
* @deprecated Use options as the second argument instead
*/
(name: string | Function, fn: (...args: T) => Awaitable<void>, options: TestOptions): void;
(name: string | Function, fn: (...args: T) => Awaitable<void>, options?: number | TestOptions): void;
(name: string | Function, options: TestOptions, fn: (...args: T) => Awaitable<void>): void;
}
interface TestEachFunction {
<T extends any[] | [any]>(cases: ReadonlyArray<T>): EachFunctionReturn<T>;
<T extends ReadonlyArray<any>>(cases: ReadonlyArray<T>): EachFunctionReturn<ExtractEachCallbackArgs<T>>;
<T>(cases: ReadonlyArray<T>): EachFunctionReturn<T[]>;
(...args: [TemplateStringsArray, ...any]): EachFunctionReturn<any[]>;
}
interface TestCollectorCallable<C = {}> {
/**
* @deprecated Use options as the second argument instead
*/
<ExtraContext extends C>(name: string | Function, fn: TestFunction<ExtraContext>, options: TestOptions): void;
<ExtraContext extends C>(name: string | Function, fn?: TestFunction<ExtraContext>, options?: number | TestOptions): void;
<ExtraContext extends C>(name: string | Function, options?: TestOptions, fn?: TestFunction<ExtraContext>): void;
}
type ChainableTestAPI<ExtraContext = {}> = ChainableFunction<'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails', TestCollectorCallable<ExtraContext>, {
each: TestEachFunction;
}>;
interface TestOptions {
/**
* Test timeout.
*/
timeout?: number;
/**
* Times to retry the test if fails. Useful for making flaky tests more stable.
* When retries is up, the last test error will be thrown.
*
* @default 0
*/
retry?: number;
/**
* How many times the test will run.
* Only inner tests will repeat if set on `describe()`, nested `describe()` will inherit parent's repeat by default.
*
* @default 0
*/
repeats?: number;
/**
* Whether tests run concurrently.
* Tests inherit `concurrent` from `describe()` and nested `describe()` will inherit from parent's `concurrent`.
*/
concurrent?: boolean;
/**
* Whether tests run sequentially.
* Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`.
*/
sequential?: boolean;
/**
* Whether the test should be skipped.
*/
skip?: boolean;
/**
* Should this test be the only one running in a suite.
*/
only?: boolean;
/**
* Whether the test should be skipped and marked as a todo.
*/
todo?: boolean;
/**
* Whether the test is expected to fail. If it does, the test will pass, otherwise it will fail.
*/
fails?: boolean;
}
interface ExtendedAPI<ExtraContext> {
skipIf: (condition: any) => ChainableTestAPI<ExtraContext>;
runIf: (condition: any) => ChainableTestAPI<ExtraContext>;
}
type CustomAPI<ExtraContext = {}> = ChainableTestAPI<ExtraContext> & ExtendedAPI<ExtraContext> & {
extend: <T extends Record<string, any> = {}>(fixtures: Fixtures<T, ExtraContext>) => CustomAPI<{
[K in keyof T | keyof ExtraContext]: K extends keyof T ? T[K] : K extends keyof ExtraContext ? ExtraContext[K] : never;
}>;
};
type TestAPI<ExtraContext = {}> = ChainableTestAPI<ExtraContext> & ExtendedAPI<ExtraContext> & {
extend: <T extends Record<string, any> = {}>(fixtures: Fixtures<T, ExtraContext>) => TestAPI<{
[K in keyof T | keyof ExtraContext]: K extends keyof T ? T[K] : K extends keyof ExtraContext ? ExtraContext[K] : never;
}>;
};
interface FixtureOptions {
/**
* Whether to automatically set up current fixture, even though it's not being used in tests.
*/
auto?: boolean;
}
type Use<T> = (value: T) => Promise<void>;
type FixtureFn<T, K extends keyof T, ExtraContext> = (context: Omit<T, K> & ExtraContext, use: Use<T[K]>) => Promise<void>;
type Fixture<T, K extends keyof T, ExtraContext = {}> = ((...args: any) => any) extends T[K] ? (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never) : T[K] | (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never);
type Fixtures<T extends Record<string, any>, ExtraContext = {}> = {
[K in keyof T]: Fixture<T, K, ExtraContext & ExtendedContext<Test>> | [Fixture<T, K, ExtraContext & ExtendedContext<Test>>, FixtureOptions?];
};
type InferFixturesTypes<T> = T extends TestAPI<infer C> ? C : T;
interface SuiteCollectorCallable<ExtraContext = {}> {
/**
* @deprecated Use options as the second argument instead
*/
<OverrideExtraContext extends ExtraContext = ExtraContext>(name: string | Function, fn: SuiteFactory<OverrideExtraContext>, options: TestOptions): SuiteCollector<OverrideExtraContext>;
<OverrideExtraContext extends ExtraContext = ExtraContext>(name: string | Function, fn?: SuiteFactory<OverrideExtraContext>, options?: number | TestOptions): SuiteCollector<OverrideExtraContext>;
<OverrideExtraContext extends ExtraContext = ExtraContext>(name: string | Function, options: TestOptions, fn?: SuiteFactory<OverrideExtraContext>): SuiteCollector<OverrideExtraContext>;
}
type ChainableSuiteAPI<ExtraContext = {}> = ChainableFunction<'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle', SuiteCollectorCallable<ExtraContext>, {
each: TestEachFunction;
}>;
type SuiteAPI<ExtraContext = {}> = ChainableSuiteAPI<ExtraContext> & {
skipIf: (condition: any) => ChainableSuiteAPI<ExtraContext>;
runIf: (condition: any) => ChainableSuiteAPI<ExtraContext>;
};
type HookListener<T extends any[], Return = void> = (...args: T) => Awaitable<Return>;
type HookCleanupCallback = (() => Awaitable<unknown>) | void;
interface SuiteHooks<ExtraContext = {}> {
beforeAll: HookListener<[Readonly<Suite | File>], HookCleanupCallback>[];
afterAll: HookListener<[Readonly<Suite | File>]>[];
beforeEach: HookListener<[ExtendedContext<Test | Custom> & ExtraContext, Readonly<Suite>], HookCleanupCallback>[];
afterEach: HookListener<[ExtendedContext<Test | Custom> & ExtraContext, Readonly<Suite>]>[];
}
interface TaskCustomOptions extends TestOptions {
concurrent?: boolean;
sequential?: boolean;
skip?: boolean;
only?: boolean;
todo?: boolean;
fails?: boolean;
each?: boolean;
meta?: Record<string, unknown>;
fixtures?: FixtureItem[];
handler?: (context: TaskContext<Custom>) => Awaitable<void>;
}
interface SuiteCollector<ExtraContext = {}> {
readonly name: string;
readonly mode: RunMode;
options?: TestOptions;
type: 'collector';
test: TestAPI<ExtraContext>;
tasks: (Suite | Custom<ExtraContext> | Test<ExtraContext> | SuiteCollector<ExtraContext>)[];
task: (name: string, options?: TaskCustomOptions) => Custom<ExtraContext>;
collect: (file?: File) => Promise<Suite>;
clear: () => void;
on: <T extends keyof SuiteHooks<ExtraContext>>(name: T, ...fn: SuiteHooks<ExtraContext>[T]) => void;
}
type SuiteFactory<ExtraContext = {}> = (test: TestAPI<ExtraContext>) => Awaitable<void>;
interface RuntimeContext {
tasks: (SuiteCollector | Test)[];
currentSuite: SuiteCollector | null;
}
interface TestContext {
}
interface TaskContext<Task extends Custom | Test = Custom | Test> {
/**
* Metadata of the current test
*/
task: Readonly<Task>;
/**
* Extract hooks on test failed
*/
onTestFailed: (fn: OnTestFailedHandler) => void;
/**
* Extract hooks on test failed
*/
onTestFinished: (fn: OnTestFinishedHandler) => void;
/**
* Mark tests as skipped. All execution after this call will be skipped.
*/
skip: () => void;
}
type ExtendedContext<T extends Custom | Test> = TaskContext<T> & TestContext;
type OnTestFailedHandler = (result: TaskResult) => Awaitable<void>;
type OnTestFinishedHandler = (result: TaskResult) => Awaitable<void>;
type SequenceHooks = 'stack' | 'list' | 'parallel';
type SequenceSetupFiles = 'list' | 'parallel';
export { type TaskContext as A, type SequenceHooks as B, type Custom as C, type DoneCallback as D, type ExtendedContext as E, type File as F, type SequenceSetupFiles as G, type HookListener as H, type InferFixturesTypes as I, type OnTestFailedHandler as O, type RunMode as R, type Suite as S, type Task as T, type Use as U, type Test as a, type ChainableFunction as b, createChainable as c, type SuiteAPI as d, type TestAPI as e, type SuiteCollector as f, type CustomAPI as g, type SuiteHooks as h, type OnTestFinishedHandler as i, type TaskState as j, type TaskBase as k, type TaskPopulated as l, type TaskMeta as m, type TaskResult as n, type TaskResultPack as o, type TestFunction as p, type TestOptions as q, type FixtureOptions as r, type FixtureFn as s, type Fixture as t, type Fixtures as u, type HookCleanupCallback as v, type TaskCustomOptions as w, type SuiteFactory as x, type RuntimeContext as y, type TestContext as z };
+121
View File
@@ -0,0 +1,121 @@
import { B as SequenceHooks, G as SequenceSetupFiles, F as File, T as Task, S as Suite, o as TaskResultPack, a as Test, C as Custom, A as TaskContext, E as ExtendedContext } from './tasks-K5XERDtv.js';
export { g as CustomAPI, D as DoneCallback, t as Fixture, s as FixtureFn, r as FixtureOptions, u as Fixtures, v as HookCleanupCallback, H as HookListener, I as InferFixturesTypes, O as OnTestFailedHandler, i as OnTestFinishedHandler, R as RunMode, y as RuntimeContext, d as SuiteAPI, f as SuiteCollector, x as SuiteFactory, h as SuiteHooks, k as TaskBase, w as TaskCustomOptions, m as TaskMeta, l as TaskPopulated, n as TaskResult, j as TaskState, e as TestAPI, z as TestContext, p as TestFunction, q as TestOptions, U as Use } from './tasks-K5XERDtv.js';
import { DiffOptions } from '@vitest/utils/diff';
import '@vitest/utils';
interface VitestRunnerConfig {
root: string;
setupFiles: string[] | string;
name: string;
passWithNoTests: boolean;
testNamePattern?: RegExp;
allowOnly?: boolean;
sequence: {
shuffle?: boolean;
concurrent?: boolean;
seed: number;
hooks: SequenceHooks;
setupFiles: SequenceSetupFiles;
};
chaiConfig?: {
truncateThreshold?: number;
};
maxConcurrency: number;
testTimeout: number;
hookTimeout: number;
retry: number;
includeTaskLocation?: boolean;
diffOptions?: DiffOptions;
}
type VitestRunnerImportSource = 'collect' | 'setup';
interface VitestRunnerConstructor {
new (config: VitestRunnerConfig): VitestRunner;
}
type CancelReason = 'keyboard-input' | 'test-failure' | string & Record<string, never>;
interface VitestRunner {
/**
* First thing that's getting called before actually collecting and running tests.
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* Called after collecting tests and before "onBeforeRun".
*/
onCollected?: (files: File[]) => unknown;
/**
* Called when test runner should cancel next test runs.
* Runner should listen for this method and mark tests and suites as skipped in
* "onBeforeRunSuite" and "onBeforeRunTask" when called.
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* Called before running a single test. Doesn't have "result" yet.
*/
onBeforeRunTask?: (test: Task) => unknown;
/**
* Called before actually running the test function. Already has "result" with "state" and "startTime".
*/
onBeforeTryTask?: (test: Task, options: {
retry: number;
repeats: number;
}) => unknown;
/**
* Called after result and state are set.
*/
onAfterRunTask?: (test: Task) => unknown;
/**
* Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws.
*/
onAfterTryTask?: (test: Task, options: {
retry: number;
repeats: number;
}) => unknown;
/**
* Called before running a single suite. Doesn't have "result" yet.
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* Called after running a single suite. Has state and result.
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* If defined, will be called instead of usual Vitest suite partition and handling.
* "before" and "after" hooks will not be ignored.
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function.
* "before" and "after" hooks will not be ignored.
*/
runTask?: (test: Task) => Promise<void>;
/**
* Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests.
*/
onTaskUpdate?: (task: TaskResultPack[]) => Promise<void>;
/**
* Called before running all tests in collected paths.
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* Called right after running all tests in collected paths.
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* Called when new context for a test is defined. Useful, if you want to add custom properties to the context.
* If you only want to define custom context, consider using "beforeAll" in "setupFiles" instead.
*
* This method is called for both "test" and "custom" handlers.
*
* @see https://vitest.dev/advanced/runner.html#your-task-function
*/
extendTaskContext?: <T extends Test | Custom>(context: TaskContext<T>) => ExtendedContext<T>;
/**
* Called, when files are imported. Can be called in two situations: when collecting tests and when importing setup files.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* Publicly available configuration.
*/
config: VitestRunnerConfig;
}
export { type CancelReason, Custom, ExtendedContext, File, SequenceHooks, SequenceSetupFiles, Suite, Task, TaskContext, TaskResultPack, Test, type VitestRunner, type VitestRunnerConfig, type VitestRunnerConstructor, type VitestRunnerImportSource };
+1
View File
@@ -0,0 +1 @@
+25
View File
@@ -0,0 +1,25 @@
import { S as Suite, T as Task, a as Test, C as Custom } from './tasks-K5XERDtv.js';
export { b as ChainableFunction, c as createChainable } from './tasks-K5XERDtv.js';
import { Arrayable } from '@vitest/utils';
/**
* If any tasks been marked as `only`, mark all other tasks as `skip`.
*/
declare function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMode?: boolean, parentIsOnly?: boolean, allowOnly?: boolean): void;
declare function someTasksAreOnly(suite: Suite): boolean;
declare function generateHash(str: string): string;
declare function calculateSuiteHash(parent: Suite): void;
/**
* Partition in tasks groups by consecutive concurrent
*/
declare function partitionSuiteChildren(suite: Suite): Task[][];
declare function getTests(suite: Arrayable<Task>): (Test | Custom)[];
declare function getTasks(tasks?: Arrayable<Task>): Task[];
declare function getSuites(suite: Arrayable<Task>): Suite[];
declare function hasTests(suite: Arrayable<Suite>): boolean;
declare function hasFailed(suite: Arrayable<Task>): boolean;
declare function getNames(task: Task): string[];
export { calculateSuiteHash, generateHash, getNames, getSuites, getTasks, getTests, hasFailed, hasTests, interpretTaskModes, partitionSuiteChildren, someTasksAreOnly };
+3
View File
@@ -0,0 +1,3 @@
export { c as calculateSuiteHash, j as createChainable, g as generateHash, f as getNames, d as getSuites, b as getTasks, a as getTests, e as hasFailed, h as hasTests, i as interpretTaskModes, p as partitionSuiteChildren, s as someTasksAreOnly } from './chunk-tasks.js';
import '@vitest/utils/error';
import '@vitest/utils';