UNPKG

@rstest/core

Version:
1,033 lines 80.1 kB
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