@rstest/core
Version:
The Rsbuild-based test tool.
1,033 lines • 80.1 kB
JavaScript
import 'module';
/*#__PURE__*/ import.meta.url;
export const __webpack_ids__ = [
"867"
];
export const __webpack_modules__ = {
"./src/runtime/api/index.ts": function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.d(__webpack_exports__, {
createRstestRuntime: ()=>createRstestRuntime
});
var dist = __webpack_require__("../../node_modules/.pnpm/@vitest+expect@3.2.4/node_modules/@vitest/expect/dist/index.js");
var src_utils = __webpack_require__("./src/utils/index.ts");
var external_chai_ = __webpack_require__("chai");
var runtime_util = __webpack_require__("./src/runtime/util.ts");
const unsupported = [
'matchSnapshot',
'toMatchSnapshot',
'toMatchInlineSnapshot',
'toThrowErrorMatchingSnapshot',
'toThrowErrorMatchingInlineSnapshot',
'throws',
'Throw',
'throw',
'toThrow',
'toThrowError'
];
function createExpectPoll(expect) {
return function poll(fn, options = {}) {
const { interval = 50, timeout = 1000, message } = options;
const assertion = expect(null, message).withContext({
poll: true
});
fn = fn.bind(assertion);
const test = external_chai_.util.flag(assertion, 'vitest-test');
if (!test) throw new Error('expect.poll() must be called inside a test');
const proxy = new Proxy(assertion, {
get (target, key, receiver) {
const assertionFunction = Reflect.get(target, key, receiver);
if ('function' != typeof assertionFunction) return assertionFunction instanceof external_chai_.Assertion ? proxy : assertionFunction;
if ('assert' === key) return assertionFunction;
if ('string' == typeof key && unsupported.includes(key)) throw new SyntaxError(`expect.poll() is not supported in combination with .${key}(). Use rstest.waitFor() if your assertion condition is unstable.`);
return function(...args) {
const STACK_TRACE_ERROR = new Error('STACK_TRACE_ERROR');
const promise = ()=>new Promise((resolve, reject)=>{
let intervalId;
let timeoutId;
let lastError;
const check = async ()=>{
try {
external_chai_.util.flag(assertion, '_name', key);
const obj = await fn();
external_chai_.util.flag(assertion, 'object', obj);
resolve(await assertionFunction.call(assertion, ...args));
clearTimeout(intervalId);
clearTimeout(timeoutId);
} catch (err) {
lastError = err;
if (!external_chai_.util.flag(assertion, '_isLastPollAttempt')) intervalId = (0, runtime_util.BH)().setTimeout(check, interval);
}
};
timeoutId = (0, runtime_util.BH)().setTimeout(()=>{
clearTimeout(intervalId);
external_chai_.util.flag(assertion, '_isLastPollAttempt', true);
const rejectWithCause = (cause)=>{
reject(copyStackTrace(new Error(`Matcher did not succeed in ${timeout}ms`, {
cause
}), STACK_TRACE_ERROR));
};
check().then(()=>rejectWithCause(lastError)).catch((e)=>rejectWithCause(e));
}, timeout);
check();
});
let awaited = false;
test.onFinished ??= [];
test.onFinished.push(()=>{
if (!awaited) {
const negated = external_chai_.util.flag(assertion, 'negate') ? 'not.' : '';
const name = external_chai_.util.flag(assertion, '_poll.element') ? 'element(locator)' : 'poll(assertion)';
const assertionString = `expect.${name}.${negated}${String(key)}()`;
const error = new Error(`${assertionString} was not awaited. This assertion is asynchronous and must be awaited; otherwise, it is not executed to avoid unhandled rejections:\n\nawait ${assertionString}\n`);
throw copyStackTrace(error, STACK_TRACE_ERROR);
}
});
let resultPromise;
return {
then (onFulfilled, onRejected) {
awaited = true;
resultPromise ||= promise();
return resultPromise.then(onFulfilled, onRejected);
},
catch (onRejected) {
resultPromise ||= promise();
return resultPromise.catch(onRejected);
},
finally (onFinally) {
resultPromise ||= promise();
return resultPromise.finally(onFinally);
},
[Symbol.toStringTag]: 'Promise'
};
};
}
});
return proxy;
};
}
function copyStackTrace(target, source) {
if (void 0 !== source.stack) target.stack = source.stack.replace(source.message, target.message);
return target;
}
var snapshot_dist = __webpack_require__("../../node_modules/.pnpm/@vitest+snapshot@3.2.4/node_modules/@vitest/snapshot/dist/index.js");
let _client;
function getSnapshotClient() {
if (!_client) _client = new snapshot_dist.xh({
isEqual: (received, expected)=>(0, dist.fS)(received, expected, [
dist.CC,
dist.gs
])
});
return _client;
}
function recordAsyncExpect(_test, promise, assertion, error) {
const test = _test;
if (test && promise instanceof Promise) {
promise = promise.finally(()=>{
if (!test.promises) return;
const index = test.promises.indexOf(promise);
if (-1 !== index) test.promises.splice(index, 1);
});
if (!test.promises) test.promises = [];
test.promises.push(promise);
let resolved = false;
test.onFinished ??= [];
test.onFinished.push(()=>{
if (!resolved) {
const processor = globalThis.__vitest_worker__?.onFilterStackTrace || ((s)=>s || '');
const stack = processor(error.stack);
console.warn([
`Promise returned by \`${assertion}\` was not awaited. `,
'Rstest currently auto-awaits hanging assertions at the end of the test.',
'Please remember to await the assertion.\n',
stack
].join(''));
}
});
return {
then (onFulfilled, onRejected) {
resolved = true;
return promise.then(onFulfilled, onRejected);
},
catch (onRejected) {
return promise.catch(onRejected);
},
finally (onFinally) {
return promise.finally(onFinally);
},
[Symbol.toStringTag]: 'Promise'
};
}
return promise;
}
function createAssertionMessage(util, assertion, hasArgs) {
const not = util.flag(assertion, 'negate') ? 'not.' : '';
const name = `${util.flag(assertion, '_name')}(${hasArgs ? 'expected' : ''})`;
const promiseName = util.flag(assertion, 'promise');
const promise = promiseName ? `.${promiseName}` : '';
return `expect(actual)${promise}.${not}${name}`;
}
function getError(expected, promise) {
if ('function' != typeof expected) {
if (!promise) throw new Error(`expected must be a function, received ${typeof expected}`);
return expected;
}
try {
expected();
} catch (e) {
return e;
}
throw new Error("snapshot function didn't throw");
}
function getTestNames(test) {
return {
filepath: test.testPath,
name: (0, src_utils.Yz)(test)
};
}
const SnapshotPlugin = (chai, utils)=>{
function getTest(assertionName, obj) {
const test = utils.flag(obj, 'vitest-test');
if (!test) throw new Error(`'${assertionName}' cannot be used without test context`);
return test;
}
for (const key of [
'matchSnapshot',
'toMatchSnapshot'
])utils.addMethod(chai.Assertion.prototype, key, function(properties, message) {
utils.flag(this, '_name', key);
const isNot = utils.flag(this, 'negate');
if (isNot) throw new Error(`${key} cannot be used with "not"`);
const expected = utils.flag(this, 'object');
const test = getTest(key, this);
if ('string' == typeof properties && void 0 === message) {
message = properties;
properties = void 0;
}
const errorMessage = utils.flag(this, 'message');
getSnapshotClient().assert({
received: expected,
message,
isInline: false,
properties,
errorMessage,
...getTestNames(test)
});
});
utils.addMethod(chai.Assertion.prototype, 'toMatchFileSnapshot', function(file, message) {
utils.flag(this, '_name', 'toMatchFileSnapshot');
const isNot = utils.flag(this, 'negate');
if (isNot) throw new Error('toMatchFileSnapshot cannot be used with "not"');
const error = new Error('resolves');
const expected = utils.flag(this, 'object');
const test = getTest('toMatchFileSnapshot', this);
const errorMessage = utils.flag(this, 'message');
const promise = getSnapshotClient().assertRaw({
received: expected,
message,
isInline: false,
rawSnapshot: {
file
},
errorMessage,
...getTestNames(test)
});
return recordAsyncExpect(test, promise, createAssertionMessage(utils, this, true), error);
});
utils.addMethod(chai.Assertion.prototype, 'toMatchInlineSnapshot', function __INLINE_SNAPSHOT__(properties, inlineSnapshot, message) {
utils.flag(this, '_name', 'toMatchInlineSnapshot');
const isNot = utils.flag(this, 'negate');
if (isNot) throw new Error('toMatchInlineSnapshot cannot be used with "not"');
const test = getTest('toMatchInlineSnapshot', this);
const isInsideEach = test.each || test.inTestEach;
if (isInsideEach) throw new Error('InlineSnapshot cannot be used inside of test.each or describe.each');
const expected = utils.flag(this, 'object');
const error = utils.flag(this, 'error');
if ('string' == typeof properties) {
message = inlineSnapshot;
inlineSnapshot = properties;
properties = void 0;
}
if (inlineSnapshot) inlineSnapshot = (0, snapshot_dist.hY)(inlineSnapshot);
const errorMessage = utils.flag(this, 'message');
getSnapshotClient().assert({
received: expected,
message,
isInline: true,
properties,
inlineSnapshot,
error,
errorMessage,
...getTestNames(test)
});
});
utils.addMethod(chai.Assertion.prototype, 'toThrowErrorMatchingSnapshot', function(message) {
utils.flag(this, '_name', 'toThrowErrorMatchingSnapshot');
const isNot = utils.flag(this, 'negate');
if (isNot) throw new Error('toThrowErrorMatchingSnapshot cannot be used with "not"');
const expected = utils.flag(this, 'object');
const test = getTest('toThrowErrorMatchingSnapshot', this);
const promise = utils.flag(this, 'promise');
const errorMessage = utils.flag(this, 'message');
getSnapshotClient().assert({
received: getError(expected, promise),
message,
errorMessage,
...getTestNames(test)
});
});
utils.addMethod(chai.Assertion.prototype, 'toThrowErrorMatchingInlineSnapshot', function __INLINE_SNAPSHOT__(inlineSnapshot, message) {
const isNot = utils.flag(this, 'negate');
if (isNot) throw new Error('toThrowErrorMatchingInlineSnapshot cannot be used with "not"');
const test = getTest('toThrowErrorMatchingInlineSnapshot', this);
const isInsideEach = test.each || test.inTestEach;
if (isInsideEach) throw new Error('InlineSnapshot cannot be used inside of test.each or describe.each');
const expected = utils.flag(this, 'object');
const error = utils.flag(this, 'error');
const promise = utils.flag(this, 'promise');
const errorMessage = utils.flag(this, 'message');
if (inlineSnapshot) inlineSnapshot = (0, snapshot_dist.hY)(inlineSnapshot);
getSnapshotClient().assert({
received: getError(expected, promise),
message,
inlineSnapshot,
isInline: true,
error,
errorMessage,
...getTestNames(test)
});
});
utils.addMethod(chai.expect, 'addSnapshotSerializer', snapshot_dist.zT);
};
external_chai_.use(dist.Dd);
external_chai_.use(dist.pT);
external_chai_.use(SnapshotPlugin);
external_chai_.use(dist.kc);
function createExpect({ getCurrentTest, workerState }) {
const expect = (value, message)=>{
const { assertionCalls } = (0, dist.y0)(expect);
(0, dist.IW)({
assertionCalls: assertionCalls + 1
}, expect);
const assert = external_chai_.expect(value, message);
const _test = getCurrentTest();
if (_test) return assert.withTest(_test);
return assert;
};
Object.assign(expect, external_chai_.expect);
Object.assign(expect, globalThis[dist.Z0]);
expect.getState = ()=>(0, dist.y0)(expect);
expect.setState = (state)=>(0, dist.IW)(state, expect);
const globalState = (0, dist.y0)(globalThis[dist.p2]) || {};
(0, dist.IW)({
...globalState,
assertionCalls: 0,
isExpectingAssertions: false,
isExpectingAssertionsError: null,
expectedAssertionsNumber: null,
expectedAssertionsNumberErrorGen: null,
get testPath () {
return workerState.testPath;
}
}, expect);
expect.extend = (matchers)=>external_chai_.expect.extend(expect, matchers);
expect.addEqualityTesters = (customTesters)=>(0, dist.uF)(customTesters);
expect.soft = (...args)=>expect(...args).withContext({
soft: true
});
expect.poll = createExpectPoll(expect);
expect.unreachable = (message)=>{
external_chai_.assert.fail(`expected ${message ? `"${message}" ` : ''}not to be reached`);
};
function assertions(expected) {
const errorGen = ()=>new Error(`expected number of assertions to be ${expected}, but got ${expect.getState().assertionCalls}`);
if (Error.captureStackTrace) Error.captureStackTrace(errorGen(), assertions);
expect.setState({
expectedAssertionsNumber: expected,
expectedAssertionsNumberErrorGen: errorGen
});
}
function hasAssertions() {
const error = new Error('expected any number of assertion, but got none');
if (Error.captureStackTrace) Error.captureStackTrace(error, hasAssertions);
expect.setState({
isExpectingAssertions: true,
isExpectingAssertionsError: error
});
}
external_chai_.util.addMethod(expect, 'assertions', assertions);
external_chai_.util.addMethod(expect, 'hasAssertions', hasAssertions);
expect.extend(dist.Nu);
return expect;
}
const normalizeFixtures = (fixtures = {}, extendFixtures = {})=>{
const result = {};
for(const key in fixtures){
const fixtureOptionKeys = [
'auto'
];
const value = fixtures[key];
if (Array.isArray(value)) {
if (1 === value.length && 'function' == typeof value[0]) {
result[key] = {
isFn: true,
value: value[0]
};
continue;
}
if ((0, src_utils.Kn)(value[1]) && Object.keys(value[1]).some((key)=>fixtureOptionKeys.includes(key))) {
result[key] = {
isFn: 'function' == typeof value[0],
value: value[0],
options: value[1]
};
continue;
}
}
result[key] = {
isFn: 'function' == typeof value,
value
};
}
const formattedResult = Object.fromEntries(Object.entries(result).map(([key, value])=>{
if (value.isFn) {
const usedProps = getFixtureUsedProps(value.value);
value.deps = usedProps.filter((p)=>p in result || p in extendFixtures);
}
return [
key,
value
];
}));
return {
...extendFixtures,
...formattedResult
};
};
const handleFixtures = async (test, context)=>{
const cleanups = [];
if (!test.fixtures) return {
cleanups
};
const doneMap = new Set();
const pendingMap = new Set();
const usedKeys = test.originalFn ? getFixtureUsedProps(test.originalFn) : [];
const useFixture = async (name, NormalizedFixture)=>{
if (doneMap.has(name)) return;
if (pendingMap.has(name)) throw new Error(`Circular fixture dependency: ${name}`);
const { isFn, deps, value: fixtureValue } = NormalizedFixture;
if (!isFn) {
context[name] = fixtureValue;
doneMap.add(name);
return;
}
pendingMap.add(name);
if (deps?.length) for (const dep of deps)await useFixture(dep, test.fixtures[dep]);
await new Promise((fixtureResolve)=>{
let useDone;
const block = fixtureValue(context, async (value)=>{
context[name] = value;
fixtureResolve();
return new Promise((useFnResolve)=>{
useDone = useFnResolve;
});
});
cleanups.unshift(()=>{
useDone?.();
return block;
});
});
doneMap.add(name);
pendingMap.delete(name);
};
for (const [name, params] of Object.entries(test.fixtures)){
const shouldAdd = params.options?.auto || usedKeys.includes(name);
if (shouldAdd) await useFixture(name, params);
}
return {
cleanups
};
};
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;
}
function filterOutComments(s) {
const result = [];
let commentState = 'none';
for(let i = 0; i < s.length; ++i)if ('singleline' === commentState) {
if ('\n' === s[i]) commentState = 'none';
} else if ('multiline' === commentState) {
if ('*' === s[i - 1] && '/' === s[i]) commentState = 'none';
} else if ('none' === commentState) if ('/' === s[i] && '/' === s[i + 1]) commentState = 'singleline';
else if ('/' === s[i] && '*' === s[i + 1]) {
commentState = 'multiline';
i += 2;
} else result.push(s[i]);
return result.join('');
}
function getFixtureUsedProps(fn) {
const text = filterOutComments(fn.toString());
const match = text.match(/(?:async)?(?:\s+function)?[^(]*\(([^)]*)/);
if (!match) return [];
const trimmedParams = match[1].trim();
if (!trimmedParams) return [];
const [firstParam] = splitByComma(trimmedParams);
if (firstParam?.[0] !== '{' || '}' !== firstParam[firstParam.length - 1]) {
if (firstParam?.startsWith('_')) return [];
throw new Error(`First argument must use the object destructuring pattern: ${firstParam}`);
}
const props = splitByComma(firstParam.substring(1, firstParam.length - 1)).map((prop)=>{
const colon = prop.indexOf(':');
return -1 === colon ? prop.trim() : prop.substring(0, colon).trim();
});
const restProperty = props.find((prop)=>prop.startsWith('...'));
if (restProperty) throw new Error(`Rest property "${restProperty}" is not supported. List all used fixtures explicitly, separated by comma.`);
return props;
}
const getTestStatus = (results, defaultStatus)=>{
if (0 === results.length) return defaultStatus;
return results.some((result)=>'fail' === result.status) ? 'fail' : results.every((result)=>'todo' === result.status) ? 'todo' : results.every((result)=>'skip' === result.status) ? 'skip' : 'pass';
};
function hasOnlyTest(test) {
return test.some((t)=>'only' === t.runMode || 'suite' === t.type && hasOnlyTest(t.tests));
}
const shouldTestSkip = (test, runOnly, testNamePattern)=>{
if (runOnly && 'only' !== test.runMode) return true;
if (testNamePattern && !(0, src_utils.Yz)(test, '').match(testNamePattern)) return true;
return false;
};
const traverseUpdateTestRunMode = (testSuite, parentRunMode, runOnly, testNamePattern)=>{
if (0 === testSuite.tests.length) return;
if (runOnly && 'only' !== testSuite.runMode && !hasOnlyTest(testSuite.tests)) testSuite.runMode = 'skip';
else if ([
'skip',
'todo'
].includes(parentRunMode)) testSuite.runMode = parentRunMode;
const tests = testSuite.tests.map((test)=>{
const runSubOnly = runOnly && 'only' !== testSuite.runMode ? runOnly : hasOnlyTest(testSuite.tests);
if ('case' === test.type) {
if ([
'skip',
'todo'
].includes(testSuite.runMode)) test.runMode = testSuite.runMode;
if (shouldTestSkip(test, runSubOnly, testNamePattern)) test.runMode = 'skip';
return test;
}
traverseUpdateTestRunMode(test, testSuite.runMode, runSubOnly, testNamePattern);
return test;
});
if ('run' !== testSuite.runMode) return;
const hasRunTest = tests.some((test)=>'run' === test.runMode || 'only' === test.runMode);
if (hasRunTest) {
testSuite.runMode = 'run';
return;
}
const allTodoTest = tests.every((test)=>'todo' === test.runMode);
testSuite.runMode = allTodoTest ? 'todo' : 'skip';
};
const updateTestModes = (tests, testNamePattern)=>{
const hasOnly = hasOnlyTest(tests);
for (const test of tests)if ('suite' === test.type) traverseUpdateTestRunMode(test, 'run', hasOnly, testNamePattern);
else if (shouldTestSkip(test, hasOnly, testNamePattern)) test.runMode = 'skip';
};
const updateTestParents = (tests, parentNames = [])=>{
for (const test of tests)if ('suite' === test.type) {
const names = test.name === src_utils.n1 ? parentNames : parentNames.concat(test.name);
updateTestParents(test.tests, names);
} else test.parentNames = parentNames;
};
const traverseUpdateTest = (tests, testNamePattern)=>{
updateTestParents(tests);
updateTestModes(tests, testNamePattern);
};
const markAllTestAsSkipped = (test)=>{
for (const t of test){
t.runMode = 'skip';
if ('suite' === t.type) markAllTestAsSkipped(t.tests);
}
};
function registerTestSuiteListener(suite, key, fn) {
const listenersKey = `${key}Listeners`;
suite[listenersKey] ??= [];
suite[listenersKey].push(fn);
}
function makeError(message, stackTraceError) {
const error = new Error(message);
if (stackTraceError?.stack) error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
return error;
}
function wrapTimeout({ name, fn, timeout, stackTraceError }) {
if (!timeout) return fn;
return async (...args)=>{
let timeoutId;
const timeoutPromise = new Promise((_, reject)=>{
timeoutId = (0, runtime_util.BH)().setTimeout(()=>reject(makeError(`${name} timed out in ${timeout}ms`, stackTraceError)), timeout);
});
try {
const result = await Promise.race([
fn(...args),
timeoutPromise
]);
timeoutId && clearTimeout(timeoutId);
return result;
} catch (error) {
timeoutId && clearTimeout(timeoutId);
throw error;
}
};
}
function limitConcurrency(concurrency = 1 / 0) {
let running = 0;
const queue = [];
const runNext = ()=>{
if (queue.length > 0 && running < concurrency) {
running++;
const next = queue.shift();
next();
}
};
return (func, ...args)=>new Promise((resolve, reject)=>{
const task = ()=>{
Promise.resolve(func(...args)).then(resolve).catch(reject).finally(()=>{
running--;
runNext();
});
};
if (running < concurrency) {
running++;
task();
} else queue.push(task);
});
}
const RealDate = Date;
class TestRunner {
_test;
workerState;
async runTests({ tests, testPath, state, hooks, api }) {
this.workerState = state;
const { runtimeConfig: { passWithNoTests, retry, maxConcurrency }, snapshotOptions } = state;
const results = [];
const errors = [];
let defaultStatus = 'pass';
hooks.onTestFileStart?.({
testPath
});
const snapshotClient = getSnapshotClient();
await snapshotClient.setup(testPath, snapshotOptions);
const runTestsCase = async (test, parentHooks)=>{
if ('skip' === test.runMode) {
snapshotClient.skipTest(testPath, (0, src_utils.Yz)(test));
const result = {
status: 'skip',
parentNames: test.parentNames,
name: test.name,
testPath
};
return result;
}
if ('todo' === test.runMode) {
const result = {
status: 'todo',
parentNames: test.parentNames,
name: test.name,
testPath
};
return result;
}
let result;
this.beforeEach(test, state, api);
const cleanups = [];
try {
for (const fn of parentHooks.beforeEachListeners){
const cleanupFn = await fn();
cleanupFn && cleanups.push(cleanupFn);
}
} catch (error) {
result = {
status: 'fail',
parentNames: test.parentNames,
name: test.name,
errors: (0, runtime_util.ov)(error, test),
testPath
};
}
if (result?.status !== 'fail') if (test.fails) try {
const fixtureCleanups = await this.beforeRunTest(test, snapshotClient.getSnapshotState(testPath));
cleanups.push(...fixtureCleanups);
await test.fn?.(test.context);
this.afterRunTest(test);
result = {
status: 'fail',
parentNames: test.parentNames,
name: test.name,
testPath,
errors: [
{
message: 'Expect test to fail'
}
]
};
} catch (_err) {
result = {
status: 'pass',
parentNames: test.parentNames,
name: test.name,
testPath
};
}
else try {
const fixtureCleanups = await this.beforeRunTest(test, snapshotClient.getSnapshotState(testPath));
cleanups.push(...fixtureCleanups);
await test.fn?.(test.context);
this.afterRunTest(test);
result = {
parentNames: test.parentNames,
name: test.name,
status: 'pass',
testPath
};
} catch (error) {
result = {
status: 'fail',
parentNames: test.parentNames,
name: test.name,
errors: (0, runtime_util.ov)(error, test),
testPath
};
}
const afterEachFns = [
...parentHooks.afterEachListeners || []
].reverse().concat(cleanups);
try {
for (const fn of afterEachFns)await fn();
} catch (error) {
result.status = 'fail';
result.errors ??= [];
result.errors.push(...(0, runtime_util.ov)(error));
}
this.resetCurrentTest();
return result;
};
const limitMaxConcurrency = limitConcurrency(maxConcurrency);
const runTests = async (allTest, parentHooks)=>{
const tests = [
...allTest
];
while(tests.length){
const suite = tests.shift();
if (suite.concurrent) {
const cases = [
suite
];
while(tests[0]?.concurrent)cases.push(tests.shift());
await Promise.all(cases.map((test)=>{
if ('suite' === test.type) return runTest(test, parentHooks);
return limitMaxConcurrency(()=>runTest(test, parentHooks));
}));
continue;
}
await runTest(suite, parentHooks);
}
};
const runTest = async (test, parentHooks)=>{
if ('suite' === test.type) {
if (0 === test.tests.length) {
if ([
'todo',
'skip'
].includes(test.runMode)) {
defaultStatus = 'skip';
return;
}
if (passWithNoTests) return;
const noTestError = {
message: `No test found in suite: ${test.name}`,
name: 'No tests'
};
errors.push(noTestError);
const result = {
status: 'fail',
parentNames: test.parentNames,
name: test.name,
testPath,
errors: [
noTestError
]
};
hooks.onTestCaseResult?.(result);
}
const cleanups = [];
let hasBeforeAllError = false;
if ([
'run',
'only'
].includes(test.runMode) && test.beforeAllListeners) try {
for (const fn of test.beforeAllListeners){
const cleanupFn = await fn({
filepath: testPath
});
cleanupFn && cleanups.push(cleanupFn);
}
} catch (error) {
hasBeforeAllError = true;
errors.push(...(0, runtime_util.ov)(error));
}
if (hasBeforeAllError) markAllTestAsSkipped(test.tests);
await runTests(test.tests, {
beforeEachListeners: parentHooks.beforeEachListeners.concat(test.beforeEachListeners || []),
afterEachListeners: parentHooks.afterEachListeners.concat(test.afterEachListeners || [])
});
const afterAllFns = [
...test.afterAllListeners || []
].reverse().concat(cleanups);
if ([
'run',
'only'
].includes(test.runMode) && afterAllFns.length) try {
for (const fn of afterAllFns)await fn({
filepath: testPath
});
} catch (error) {
errors.push(...(0, runtime_util.ov)(error));
}
} else {
const start = RealDate.now();
let result;
let retryCount = 0;
do {
const currentResult = await runTestsCase(test, parentHooks);
result = {
...currentResult,
errors: 'fail' === currentResult.status && result && result.errors ? result.errors.concat(...currentResult.errors || []) : currentResult.errors
};
retryCount++;
}while (retryCount <= retry && 'fail' === result.status);
result.duration = RealDate.now() - start;
result.retryCount = retryCount - 1;
hooks.onTestCaseResult?.(result);
results.push(result);
}
};
const start = RealDate.now();
if (0 === tests.length) {
if (passWithNoTests) return {
testPath,
name: '',
status: 'pass',
results
};
return {
testPath,
name: '',
status: 'fail',
results,
errors: [
{
message: `No test suites found in file: ${testPath}`,
name: 'No tests'
}
]
};
}
await runTests(tests, {
beforeEachListeners: [],
afterEachListeners: []
});
const snapshotResult = await snapshotClient.finish(testPath);
return {
testPath,
name: '',
status: errors.length ? 'fail' : getTestStatus(results, defaultStatus),
results,
snapshotResult,
errors,
duration: RealDate.now() - start
};
}
resetCurrentTest() {
this._test = void 0;
}
setCurrentTest(test) {
this._test = test;
}
getCurrentTest() {
return this._test;
}
beforeEach(test, state, api) {
const { runtimeConfig: { clearMocks, resetMocks, restoreMocks, unstubEnvs, unstubGlobals } } = state;
this.setCurrentTest(test);
if (restoreMocks) api.rstest.restoreAllMocks();
else if (resetMocks) api.rstest.resetAllMocks();
else if (clearMocks) api.rstest.clearAllMocks();
if (unstubEnvs) api.rstest.unstubAllEnvs();
if (unstubGlobals) api.rstest.unstubAllGlobals();
}
createTestContext() {
const context = ()=>{
throw new Error('done() callback is deprecated, use promise instead');
};
let _expect;
const current = this._test;
Object.defineProperty(context, 'expect', {
get: ()=>{
if (!_expect) _expect = createExpect({
workerState: this.workerState,
getCurrentTest: ()=>current
});
return _expect;
}
});
Object.defineProperty(context, '_useLocalExpect', {
get () {
return null != _expect;
}
});
return context;
}
async beforeRunTest(test, snapshotState) {
(0, dist.IW)({
assertionCalls: 0,
isExpectingAssertions: false,
isExpectingAssertionsError: null,
expectedAssertionsNumber: null,
expectedAssertionsNumberErrorGen: null,
testPath: test.testPath,
snapshotState,
currentTestName: (0, src_utils.Yz)(test)
}, globalThis[dist.p2]);
const context = this.createTestContext();
const { cleanups } = await handleFixtures(test, context);
Object.defineProperty(test, 'context', {
value: context,
enumerable: false
});
return cleanups;
}
afterRunTest(test) {
const expect = test.context._useLocalExpect ? test.context.expect : globalThis[dist.p2];
const { assertionCalls, expectedAssertionsNumber, expectedAssertionsNumberErrorGen, isExpectingAssertions, isExpectingAssertionsError } = (0, dist.y0)(expect);
if (test.result?.state === 'fail') throw test.result.errors;
if (null !== expectedAssertionsNumber && assertionCalls !== expectedAssertionsNumber) throw expectedAssertionsNumberErrorGen();
if (true === isExpectingAssertions && 0 === assertionCalls) throw isExpectingAssertionsError;
}
}
class RunnerRuntime {
tests = [];
_currentTest = [];
testPath;
status = 'collect';
collectStatus = 'lazy';
currentCollectList = [];
runtimeConfig;
constructor({ testPath, runtimeConfig }){
this.testPath = testPath;
this.runtimeConfig = runtimeConfig;
}
updateStatus(status) {
this.status = status;
}
checkStatus(name, type) {
if ('running' === this.status) {
const error = new runtime_util.Ni(`${'case' === type ? 'Test' : 'Describe'} '${name}' cannot run`);
throw error;
}
}
afterAll(fn, timeout = this.runtimeConfig.hookTimeout) {
const currentSuite = this.getCurrentSuite();
registerTestSuiteListener(currentSuite, 'afterAll', wrapTimeout({
name: 'afterAll hook',
fn,
timeout,
stackTraceError: new Error('STACK_TRACE_ERROR')
}));
}
beforeAll(fn, timeout = this.runtimeConfig.hookTimeout) {
const currentSuite = this.getCurrentSuite();
registerTestSuiteListener(currentSuite, 'beforeAll', wrapTimeout({
name: 'beforeAll hook',
fn,
timeout,
stackTraceError: new Error('STACK_TRACE_ERROR')
}));
}
afterEach(fn, timeout = this.runtimeConfig.hookTimeout) {
const currentSuite = this.getCurrentSuite();
registerTestSuiteListener(currentSuite, 'afterEach', wrapTimeout({
name: 'afterEach hook',
fn,
timeout,
stackTraceError: new Error('STACK_TRACE_ERROR')
}));
}
beforeEach(fn, timeout = this.runtimeConfig.hookTimeout) {
const currentSuite = this.getCurrentSuite();
registerTestSuiteListener(currentSuite, 'beforeEach', wrapTimeout({
name: 'beforeEach hook',
fn,
timeout,
stackTraceError: new Error('STACK_TRACE_ERROR')
}));
}
getDefaultRootSuite() {
return {
runMode: 'run',
testPath: this.testPath,
name: src_utils.n1,
tests: [],
type: 'suite'
};
}
describe({ name, fn, runMode = 'run', each = false, concurrent, sequential }) {
this.checkStatus(name, 'suite');
const currentSuite = {
name,
runMode,
tests: [],
type: 'suite',
each,
testPath: this.testPath,
concurrent,
sequential
};
if (!fn) {
this.addTest(currentSuite);
this.resetCurrentTest();
return;
}
this.collectStatus = 'lazy';
this.currentCollectList.push(async ()=>{
this.addTest(currentSuite);
const result = fn();
if (result instanceof Promise) await result;
await this.collectCurrentTest();
this.resetCurrentTest();
});
}
resetCurrentTest() {
this._currentTest.pop();
}
addTest(test) {
if (0 === this._currentTest.length) this.tests.push(test);
else {
const current = this._currentTest[this._currentTest.length - 1];
if (current.each || current.inTestEach) test.inTestEach = true;
if (current.concurrent && true !== test.sequential) test.concurrent = true;
if (current.sequential && true !== test.concurrent) test.sequential = true;
if ('case' === current.type) throw new Error('Calling the test function inside another test function is not allowed. Please put it inside "describe" so it can be properly collected.');
current.tests.push(test);
}
this._currentTest.push(test);
}
async collectCurrentTest() {
const currentCollectList