UNPKG

jest-circus

Version:
1,249 lines (1,235 loc) 44.6 kB
import { createRequire } from "node:module"; import { jestExpect } from "@jest/expect"; import { createEmptyTestResult } from "@jest/test-result"; import { formatExecError, formatResultsErrors } from "jest-message-util"; import { SnapshotState, addSerializer, buildSnapshotResolver } from "jest-snapshot"; import { bind } from "jest-each"; import { ErrorWithStack, convertDescriptorToString, formatTime, invariant, isPromise, protectProperties, setGlobal } from "jest-util"; import * as path from "path"; import co from "co"; import dedent from "dedent"; import isGeneratorFn from "is-generator-fn"; import slash from "slash"; import StackUtils from "stack-utils"; import { format } from "pretty-format"; import { AssertionError } from "assert"; import chalk from "chalk"; import { diff, printExpected, printReceived } from "jest-matcher-utils"; import { AsyncLocalStorage } from "async_hooks"; import pLimit from "p-limit"; import { unsafeUniformIntDistribution, xoroshiro128plus } from "pure-rand"; //#region rolldown:runtime var __require = /* @__PURE__ */ createRequire(import.meta.url); //#endregion //#region src/globalErrorHandlers.ts const uncaughtExceptionListener = (error) => { dispatchSync({ error, name: "error" }); }; const unhandledRejectionListener = (error, promise) => { dispatchSync({ error, name: "error", promise }); }; const rejectionHandledListener = (promise) => { dispatchSync({ name: "error_handled", promise }); }; const injectGlobalErrorHandlers = (parentProcess) => { const uncaughtException = [...process.listeners("uncaughtException")]; const unhandledRejection = [...process.listeners("unhandledRejection")]; const rejectionHandled = [...process.listeners("rejectionHandled")]; parentProcess.removeAllListeners("uncaughtException"); parentProcess.removeAllListeners("unhandledRejection"); parentProcess.removeAllListeners("rejectionHandled"); parentProcess.on("uncaughtException", uncaughtExceptionListener); parentProcess.on("unhandledRejection", unhandledRejectionListener); parentProcess.on("rejectionHandled", rejectionHandledListener); return { rejectionHandled, uncaughtException, unhandledRejection }; }; const restoreGlobalErrorHandlers = (parentProcess, originalErrorHandlers) => { parentProcess.removeListener("uncaughtException", uncaughtExceptionListener); parentProcess.removeListener("unhandledRejection", unhandledRejectionListener); parentProcess.removeListener("rejectionHandled", rejectionHandledListener); for (const listener of originalErrorHandlers.uncaughtException) parentProcess.on("uncaughtException", listener); for (const listener of originalErrorHandlers.unhandledRejection) parentProcess.on("unhandledRejection", listener); for (const listener of originalErrorHandlers.rejectionHandled) parentProcess.on("rejectionHandled", listener); }; //#endregion //#region src/types.ts /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const STATE_SYM = Symbol("JEST_STATE_SYMBOL"); const RETRY_TIMES = Symbol.for("RETRY_TIMES"); const RETRY_IMMEDIATELY = Symbol.for("RETRY_IMMEDIATELY"); const WAIT_BEFORE_RETRY = Symbol.for("WAIT_BEFORE_RETRY"); const TEST_TIMEOUT_SYMBOL = Symbol.for("TEST_TIMEOUT_SYMBOL"); const EVENT_HANDLERS = Symbol.for("EVENT_HANDLERS"); const LOG_ERRORS_BEFORE_RETRY = Symbol.for("LOG_ERRORS_BEFORE_RETRY"); //#endregion //#region src/utils.ts const stackUtils = new StackUtils({ cwd: "A path that does not exist" }); const jestEachBuildDir = slash(path.dirname(__require.resolve("jest-each"))); function takesDoneCallback(fn) { return fn.length > 0; } function isGeneratorFunction(fn) { return isGeneratorFn(fn); } const makeDescribe = (name, parent, mode) => { let _mode = mode; if (parent && !mode) _mode = parent.mode; return { type: "describeBlock", children: [], hooks: [], mode: _mode, name: convertDescriptorToString(name), parent, tests: [] }; }; const makeTest = (fn, mode, concurrent, name, parent, timeout, asyncError, failing) => ({ type: "test", asyncError, concurrent, duration: null, errors: [], failing, fn, invocations: 0, mode, name: convertDescriptorToString(name), numPassingAsserts: 0, parent, retryReasons: [], seenDone: false, startedAt: null, status: null, timeout, unhandledRejectionErrorByPromise: /* @__PURE__ */ new Map() }); const hasEnabledTest = (describeBlock) => { const { hasFocusedTests, testNamePattern } = getState(); return describeBlock.children.some((child) => child.type === "describeBlock" ? hasEnabledTest(child) : !(child.mode === "skip" || hasFocusedTests && child.mode !== "only" || testNamePattern && !testNamePattern.test(getTestID(child)))); }; const getAllHooksForDescribe = (describe$1) => { const result = { afterAll: [], beforeAll: [] }; if (hasEnabledTest(describe$1)) for (const hook of describe$1.hooks) switch (hook.type) { case "beforeAll": result.beforeAll.push(hook); break; case "afterAll": result.afterAll.push(hook); break; } return result; }; const getEachHooksForTest = (test$1) => { const result = { afterEach: [], beforeEach: [] }; if (test$1.concurrent) return result; let block = test$1.parent; do { const beforeEachForCurrentBlock = []; for (const hook of block.hooks) switch (hook.type) { case "beforeEach": beforeEachForCurrentBlock.push(hook); break; case "afterEach": result.afterEach.push(hook); break; } result.beforeEach.unshift(...beforeEachForCurrentBlock); } while (block = block.parent); return result; }; const describeBlockHasTests = (describe$1) => describe$1.children.some((child) => child.type === "test" || describeBlockHasTests(child)); const _makeTimeoutMessage = (timeout, isHook, takesDoneCallback$1) => `Exceeded timeout of ${formatTime(timeout)} for a ${isHook ? "hook" : "test"}${takesDoneCallback$1 ? " while waiting for `done()` to be called" : ""}.\nAdd a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout.`; const { setTimeout: setTimeout$2, clearTimeout } = globalThis; function checkIsError(error) { return !!(error && error.message && error.stack); } const callAsyncCircusFn = (testOrHook, testContext, { isHook, timeout }) => { let timeoutID; let completed = false; const { fn, asyncError } = testOrHook; const doneCallback = takesDoneCallback(fn); return new Promise((resolve, reject) => { timeoutID = setTimeout$2(() => reject(_makeTimeoutMessage(timeout, isHook, doneCallback)), timeout); if (doneCallback) { let returnedValue$1 = void 0; const done = (reason) => { const errorAtDone = new ErrorWithStack(void 0, done); if (!completed && testOrHook.seenDone) { errorAtDone.message = "Expected done to be called once, but it was called multiple times."; if (reason) errorAtDone.message += ` Reason: ${format(reason, { maxDepth: 3 })}`; reject(errorAtDone); throw errorAtDone; } else testOrHook.seenDone = true; Promise.resolve().then(() => { if (returnedValue$1 !== void 0) { asyncError.message = dedent` Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise. Returned value: ${format(returnedValue$1, { maxDepth: 3 })} `; return reject(asyncError); } let errorAsErrorObject; if (checkIsError(reason)) errorAsErrorObject = reason; else { errorAsErrorObject = errorAtDone; errorAtDone.message = `Failed: ${format(reason, { maxDepth: 3 })}`; } if (completed && reason) { errorAsErrorObject.message = `Caught error after test environment was torn down\n\n${errorAsErrorObject.message}`; throw errorAsErrorObject; } return reason ? reject(errorAsErrorObject) : resolve(); }); }; returnedValue$1 = fn.call(testContext, done); return; } let returnedValue; if (isGeneratorFunction(fn)) returnedValue = co.wrap(fn).call({}); else try { returnedValue = fn.call(testContext); } catch (error) { reject(error); return; } if (isPromise(returnedValue)) { returnedValue.then(() => resolve(), reject); return; } if (!isHook && returnedValue !== void 0) { reject(new Error(dedent` test functions can only return Promise or undefined. Returned value: ${format(returnedValue, { maxDepth: 3 })} `)); return; } resolve(); }).finally(() => { completed = true; timeoutID.unref?.(); clearTimeout(timeoutID); }); }; const getTestDuration = (test$1) => { const { startedAt } = test$1; return typeof startedAt === "number" ? Date.now() - startedAt : null; }; const makeRunResult = (describeBlock, unhandledErrors) => ({ testResults: makeTestResults(describeBlock), unhandledErrors: unhandledErrors.map(_getError).map(getErrorStack) }); const getTestNamesPath = (test$1) => { const titles = []; let parent = test$1; do titles.unshift(parent.name); while (parent = parent.parent); return titles; }; const makeSingleTestResult = (test$1) => { const { includeTestLocationInResult } = getState(); const { status } = test$1; invariant(status, "Status should be present after tests are run."); const testPath = getTestNamesPath(test$1); let location = null; if (includeTestLocationInResult) { const stackLines = test$1.asyncError.stack.split("\n"); const stackLine = stackLines[1]; let parsedLine = stackUtils.parseLine(stackLine); if (parsedLine?.file?.startsWith(jestEachBuildDir)) { const stackLine$1 = stackLines[2]; parsedLine = stackUtils.parseLine(stackLine$1); } if (parsedLine && typeof parsedLine.column === "number" && typeof parsedLine.line === "number") location = { column: parsedLine.column, line: parsedLine.line }; } const errorsDetailed = test$1.errors.map(_getError); return { duration: test$1.duration, errors: errorsDetailed.map(getErrorStack), errorsDetailed, failing: test$1.failing, invocations: test$1.invocations, location, numPassingAsserts: test$1.numPassingAsserts, retryReasons: test$1.retryReasons.map(_getError).map(getErrorStack), startedAt: test$1.startedAt, status, testPath: [...testPath] }; }; const makeTestResults = (describeBlock) => { const testResults = []; const stack = [[describeBlock, 0]]; while (stack.length > 0) { const [currentBlock, childIndex] = stack.pop(); for (let i = childIndex; i < currentBlock.children.length; i++) { const child = currentBlock.children[i]; if (child.type === "describeBlock") { stack.push([currentBlock, i + 1], [child, 0]); break; } if (child.type === "test") testResults.push(makeSingleTestResult(child)); } } return testResults; }; const getTestID = (test$1) => { const testNamesPath = getTestNamesPath(test$1); testNamesPath.shift(); return testNamesPath.join(" "); }; const _getError = (errors) => { let error; let asyncError; if (Array.isArray(errors)) { error = errors[0]; asyncError = errors[1]; } else { error = errors; asyncError = new Error(); } if (error && (typeof error.stack === "string" || error.message)) return error; asyncError.message = `thrown: ${format(error, { maxDepth: 3 })}`; return asyncError; }; const getErrorStack = (error) => typeof error.stack === "string" ? error.stack : error.message; const addErrorToEachTestUnderDescribe = (describeBlock, error, asyncError) => { for (const child of describeBlock.children) switch (child.type) { case "describeBlock": addErrorToEachTestUnderDescribe(child, error, asyncError); break; case "test": child.errors.push([error, asyncError]); break; } }; const resolveTestCaseStartInfo = (testNamesPath) => { const ancestorTitles = testNamesPath.filter((name) => name !== ROOT_DESCRIBE_BLOCK_NAME); const fullName = ancestorTitles.join(" "); const title = testNamesPath.at(-1); ancestorTitles.pop(); return { ancestorTitles, fullName, title }; }; const parseSingleTestResult = (testResult) => { let status; if (testResult.status === "skip") status = "pending"; else if (testResult.status === "todo") status = "todo"; else if (testResult.errors.length > 0) status = "failed"; else status = "passed"; const { ancestorTitles, fullName, title } = resolveTestCaseStartInfo(testResult.testPath); return { ancestorTitles, duration: testResult.duration, failing: testResult.failing, failureDetails: testResult.errorsDetailed, failureMessages: [...testResult.errors], fullName, invocations: testResult.invocations, location: testResult.location, numPassingAsserts: testResult.numPassingAsserts, retryReasons: [...testResult.retryReasons], startedAt: testResult.startedAt, status, title }; }; const createTestCaseStartInfo = (test$1) => { const testPath = getTestNamesPath(test$1); const { ancestorTitles, fullName, title } = resolveTestCaseStartInfo(testPath); return { ancestorTitles, fullName, mode: test$1.mode, startedAt: test$1.startedAt, title }; }; //#endregion //#region src/eventHandler.ts const eventHandler$1 = (event, state) => { switch (event.name) { case "include_test_location_in_result": { state.includeTestLocationInResult = true; break; } case "hook_start": { event.hook.seenDone = false; break; } case "start_describe_definition": { const { blockName, mode } = event; const { currentDescribeBlock, currentlyRunningTest } = state; if (currentlyRunningTest) { currentlyRunningTest.errors.push(new Error(`Cannot nest a describe inside a test. Describe block "${blockName}" cannot run because it is nested within "${currentlyRunningTest.name}".`)); break; } const describeBlock = makeDescribe(blockName, currentDescribeBlock, mode); currentDescribeBlock.children.push(describeBlock); state.currentDescribeBlock = describeBlock; break; } case "finish_describe_definition": { const { currentDescribeBlock } = state; invariant(currentDescribeBlock, "currentDescribeBlock must be there"); if (!describeBlockHasTests(currentDescribeBlock)) for (const hook of currentDescribeBlock.hooks) { hook.asyncError.message = `Invalid: ${hook.type}() may not be used in a describe block containing no tests.`; state.unhandledErrors.push(hook.asyncError); } const shouldPassMode = !(currentDescribeBlock.mode === "only" && currentDescribeBlock.children.some((child) => child.type === "test" && child.mode === "only")); if (shouldPassMode) { for (const child of currentDescribeBlock.children) if (child.type === "test" && !child.mode) child.mode = currentDescribeBlock.mode; } if (!state.hasFocusedTests && currentDescribeBlock.mode !== "skip" && currentDescribeBlock.children.some((child) => child.type === "test" && child.mode === "only")) state.hasFocusedTests = true; if (currentDescribeBlock.parent) state.currentDescribeBlock = currentDescribeBlock.parent; break; } case "add_hook": { const { currentDescribeBlock, currentlyRunningTest, hasStarted } = state; const { asyncError, fn, hookType: type, timeout } = event; if (currentlyRunningTest) { currentlyRunningTest.errors.push(new Error(`Hooks cannot be defined inside tests. Hook of type "${type}" is nested within "${currentlyRunningTest.name}".`)); break; } else if (hasStarted) { state.unhandledErrors.push(new Error("Cannot add a hook after tests have started running. Hooks must be defined synchronously.")); break; } const parent = currentDescribeBlock; currentDescribeBlock.hooks.push({ asyncError, fn, parent, seenDone: false, timeout, type }); break; } case "add_test": { const { currentDescribeBlock, currentlyRunningTest, hasStarted } = state; const { asyncError, fn, mode, testName: name, timeout, concurrent, failing } = event; if (currentlyRunningTest) { currentlyRunningTest.errors.push(new Error(`Tests cannot be nested. Test "${name}" cannot run because it is nested within "${currentlyRunningTest.name}".`)); break; } else if (hasStarted) { state.unhandledErrors.push(new Error("Cannot add a test after tests have started running. Tests must be defined synchronously.")); break; } const test$1 = makeTest(fn, mode, concurrent, name, currentDescribeBlock, timeout, asyncError, failing); if (currentDescribeBlock.mode !== "skip" && test$1.mode === "only") state.hasFocusedTests = true; currentDescribeBlock.children.push(test$1); currentDescribeBlock.tests.push(test$1); break; } case "hook_failure": { const { test: test$1, describeBlock, error, hook } = event; const { asyncError, type } = hook; if (type === "beforeAll") { invariant(describeBlock, "always present for `*All` hooks"); addErrorToEachTestUnderDescribe(describeBlock, error, asyncError); } else if (type === "afterAll") state.unhandledErrors.push([error, asyncError]); else { invariant(test$1, "always present for `*Each` hooks"); test$1.errors.push([error, asyncError]); } break; } case "test_skip": { event.test.status = "skip"; break; } case "test_todo": { event.test.status = "todo"; break; } case "test_done": { event.test.duration = getTestDuration(event.test); event.test.status = "done"; state.currentlyRunningTest = null; break; } case "test_start": { state.currentlyRunningTest = event.test; event.test.startedAt = Date.now(); event.test.invocations += 1; break; } case "test_fn_start": { event.test.seenDone = false; break; } case "test_fn_failure": { const { error, test: { asyncError } } = event; event.test.errors.push([error, asyncError]); break; } case "test_retry": { const logErrorsBeforeRetry = globalThis[LOG_ERRORS_BEFORE_RETRY] || false; if (logErrorsBeforeRetry) event.test.retryReasons.push(...event.test.errors); event.test.errors = []; break; } case "run_start": { state.hasStarted = true; if (globalThis[TEST_TIMEOUT_SYMBOL]) state.testTimeout = globalThis[TEST_TIMEOUT_SYMBOL]; break; } case "run_finish": break; case "setup": { state.parentProcess = event.parentProcess; invariant(state.parentProcess); state.originalGlobalErrorHandlers = injectGlobalErrorHandlers(state.parentProcess); if (event.testNamePattern) state.testNamePattern = new RegExp(event.testNamePattern, "i"); break; } case "teardown": { invariant(state.originalGlobalErrorHandlers); invariant(state.parentProcess); restoreGlobalErrorHandlers(state.parentProcess, state.originalGlobalErrorHandlers); break; } case "error": { if (state.currentlyRunningTest) if (event.promise) state.currentlyRunningTest.unhandledRejectionErrorByPromise.set(event.promise, event.error); else state.currentlyRunningTest.errors.push(event.error); else if (event.promise) state.unhandledRejectionErrorByPromise.set(event.promise, event.error); else state.unhandledErrors.push(event.error); break; } case "error_handled": { if (state.currentlyRunningTest) state.currentlyRunningTest.unhandledRejectionErrorByPromise.delete(event.promise); else state.unhandledRejectionErrorByPromise.delete(event.promise); break; } } }; var eventHandler_default = eventHandler$1; //#endregion //#region src/formatNodeAssertErrors.ts const assertOperatorsMap = { "!=": "notEqual", "!==": "notStrictEqual", "==": "equal", "===": "strictEqual" }; const humanReadableOperators = { deepEqual: "to deeply equal", deepStrictEqual: "to deeply and strictly equal", equal: "to be equal", notDeepEqual: "not to deeply equal", notDeepStrictEqual: "not to deeply and strictly equal", notEqual: "to not be equal", notStrictEqual: "not be strictly equal", strictEqual: "to strictly be equal" }; const formatNodeAssertErrors = (event, state) => { if (event.name === "test_done") event.test.errors = event.test.errors.map((errors) => { let error; if (Array.isArray(errors)) { const [originalError, asyncError] = errors; if (originalError == null) error = asyncError; else if (originalError.stack) error = originalError; else { error = asyncError; error.message = originalError.message || `thrown: ${format(originalError, { maxDepth: 3 })}`; } } else error = errors; return isAssertionError(error) ? { message: assertionErrorMessage(error, { expand: state.expand }) } : errors; }); }; const getOperatorName = (operator, stack) => { if (typeof operator === "string") return assertOperatorsMap[operator] || operator; if (stack.match(".doesNotThrow")) return "doesNotThrow"; if (stack.match(".throws")) return "throws"; return ""; }; const operatorMessage = (operator) => { const niceOperatorName = getOperatorName(operator, ""); const humanReadableOperator = humanReadableOperators[niceOperatorName]; return typeof operator === "string" ? `${humanReadableOperator || niceOperatorName} to:\n` : ""; }; const assertThrowingMatcherHint = (operatorName) => operatorName ? chalk.dim("assert") + chalk.dim(`.${operatorName}(`) + chalk.red("function") + chalk.dim(")") : ""; const assertMatcherHint = (operator, operatorName, expected) => { let message = ""; if (operator === "==" && expected === true) message = chalk.dim("assert") + chalk.dim("(") + chalk.red("received") + chalk.dim(")"); else if (operatorName) message = chalk.dim("assert") + chalk.dim(`.${operatorName}(`) + chalk.red("received") + chalk.dim(", ") + chalk.green("expected") + chalk.dim(")"); return message; }; function assertionErrorMessage(error, options) { const { expected, actual, generatedMessage, message, operator, stack } = error; const diffString = diff(expected, actual, options); const hasCustomMessage = !generatedMessage; const operatorName = getOperatorName(operator, stack); const trimmedStack = stack.replace(message, "").replaceAll(/AssertionError(.*)/g, ""); if (operatorName === "doesNotThrow") return buildHintString(assertThrowingMatcherHint(operatorName)) + chalk.reset("Expected the function not to throw an error.\n") + chalk.reset("Instead, it threw:\n") + ` ${printReceived(actual)}` + chalk.reset(hasCustomMessage ? `\n\nMessage:\n ${message}` : "") + trimmedStack; if (operatorName === "throws") { if (error.generatedMessage) return buildHintString(assertThrowingMatcherHint(operatorName)) + chalk.reset(error.message) + chalk.reset(hasCustomMessage ? `\n\nMessage:\n ${message}` : "") + trimmedStack; return buildHintString(assertThrowingMatcherHint(operatorName)) + chalk.reset("Expected the function to throw an error.\n") + chalk.reset("But it didn't throw anything.") + chalk.reset(hasCustomMessage ? `\n\nMessage:\n ${message}` : "") + trimmedStack; } if (operatorName === "fail") return buildHintString(assertMatcherHint(operator, operatorName, expected)) + chalk.reset(hasCustomMessage ? `Message:\n ${message}` : "") + trimmedStack; return buildHintString(assertMatcherHint(operator, operatorName, expected)) + chalk.reset(`Expected value ${operatorMessage(operator)}`) + ` ${printExpected(expected)}\n` + chalk.reset("Received:\n") + ` ${printReceived(actual)}` + chalk.reset(hasCustomMessage ? `\n\nMessage:\n ${message}` : "") + (diffString ? `\n\nDifference:\n\n${diffString}` : "") + trimmedStack; } function isAssertionError(error) { return error && (error instanceof AssertionError || error.name === AssertionError.name || error.code === "ERR_ASSERTION"); } function buildHintString(hint) { return hint ? `${hint}\n\n` : ""; } var formatNodeAssertErrors_default = formatNodeAssertErrors; //#endregion //#region src/state.ts const handlers = globalThis[EVENT_HANDLERS] || [eventHandler_default, formatNodeAssertErrors_default]; setGlobal(globalThis, EVENT_HANDLERS, handlers, "retain"); const ROOT_DESCRIBE_BLOCK_NAME = "ROOT_DESCRIBE_BLOCK"; const createState = () => { const ROOT_DESCRIBE_BLOCK = makeDescribe(ROOT_DESCRIBE_BLOCK_NAME); return { currentDescribeBlock: ROOT_DESCRIBE_BLOCK, currentlyRunningTest: null, expand: void 0, hasFocusedTests: false, hasStarted: false, includeTestLocationInResult: false, maxConcurrency: 5, parentProcess: null, rootDescribeBlock: ROOT_DESCRIBE_BLOCK, seed: 0, testNamePattern: null, testTimeout: 5e3, unhandledErrors: [], unhandledRejectionErrorByPromise: /* @__PURE__ */ new Map() }; }; const getState = () => globalThis[STATE_SYM]; const setState = (state) => { setGlobal(globalThis, STATE_SYM, state); protectProperties(state, [ "hasFocusedTests", "hasStarted", "includeTestLocationInResult", "maxConcurrency", "seed", "testNamePattern", "testTimeout", "unhandledErrors", "unhandledRejectionErrorByPromise" ]); return state; }; const resetState = () => { setState(createState()); }; resetState(); const dispatch = async (event) => { for (const handler of handlers) await handler(event, getState()); }; const dispatchSync = (event) => { for (const handler of handlers) handler(event, getState()); }; const addEventHandler = (handler) => { handlers.push(handler); }; //#endregion //#region src/shuffleArray.ts const rngBuilder = (seed) => { const gen = xoroshiro128plus(seed); return { next: (from, to) => unsafeUniformIntDistribution(from, to, gen) }; }; function shuffleArray(array, random) { const length = array.length; if (length === 0) return []; for (let i = 0; i < length; i++) { const n = random.next(i, length - 1); const value = array[i]; array[i] = array[n]; array[n] = value; } return array; } //#endregion //#region src/run.ts const { setTimeout: setTimeout$1 } = globalThis; const run = async () => { const { rootDescribeBlock, seed, randomize } = getState(); const rng = randomize ? rngBuilder(seed) : void 0; await dispatch({ name: "run_start" }); await _runTestsForDescribeBlock(rootDescribeBlock, rng, true); await dispatch({ name: "run_finish" }); return makeRunResult(getState().rootDescribeBlock, getState().unhandledErrors); }; const _runTestsForDescribeBlock = async (describeBlock, rng, isRootBlock = false) => { await dispatch({ describeBlock, name: "run_describe_start" }); const { beforeAll: beforeAll$1, afterAll: afterAll$1 } = getAllHooksForDescribe(describeBlock); const isSkipped = describeBlock.mode === "skip"; if (!isSkipped) for (const hook of beforeAll$1) await _callCircusHook({ describeBlock, hook }); if (isRootBlock) { const concurrentTests$1 = collectConcurrentTests(describeBlock); if (concurrentTests$1.length > 0) startTestsConcurrently(concurrentTests$1, isSkipped); } const retryTimes = Number.parseInt(globalThis[RETRY_TIMES], 10) || 0; const waitBeforeRetry = Number.parseInt(globalThis[WAIT_BEFORE_RETRY], 10) || 0; const retryImmediately = globalThis[RETRY_IMMEDIATELY] || false; const deferredRetryTests = []; if (rng) describeBlock.children = shuffleArray(describeBlock.children, rng); const rerunTest = async (test$1) => { let numRetriesAvailable = retryTimes; while (numRetriesAvailable > 0 && test$1.errors.length > 0) { await dispatch({ name: "test_retry", test: test$1 }); if (waitBeforeRetry > 0) await new Promise((resolve) => setTimeout$1(resolve, waitBeforeRetry)); await _runTest(test$1, isSkipped); numRetriesAvailable--; } }; const handleRetry = async (test$1, hasErrorsBeforeTestRun, hasRetryTimes) => { if (test$1.errors.length === 0 || hasErrorsBeforeTestRun || !hasRetryTimes) return; if (!retryImmediately) { deferredRetryTests.push(test$1); return; } await rerunTest(test$1); }; const concurrentTests = []; for (const child of describeBlock.children) switch (child.type) { case "describeBlock": { await _runTestsForDescribeBlock(child, rng); break; } case "test": { const hasErrorsBeforeTestRun = child.errors.length > 0; const hasRetryTimes = retryTimes > 0; if (child.concurrent) concurrentTests.push(child.done.then(() => handleRetry(child, hasErrorsBeforeTestRun, hasRetryTimes))); else { await _runTest(child, isSkipped); await handleRetry(child, hasErrorsBeforeTestRun, hasRetryTimes); } break; } } await Promise.all(concurrentTests); for (const test$1 of deferredRetryTests) await rerunTest(test$1); if (!isSkipped) for (const hook of afterAll$1) await _callCircusHook({ describeBlock, hook }); await dispatch({ describeBlock, name: "run_describe_finish" }); }; function collectConcurrentTests(describeBlock) { if (describeBlock.mode === "skip") return []; return describeBlock.children.flatMap((child) => { switch (child.type) { case "describeBlock": return collectConcurrentTests(child); case "test": if (child.concurrent) return [child]; return []; } }); } function startTestsConcurrently(concurrentTests, parentSkipped) { const mutex = pLimit(getState().maxConcurrency); const testNameStorage = new AsyncLocalStorage(); jestExpect.setState({ currentConcurrentTestName: () => testNameStorage.getStore() }); for (const test$1 of concurrentTests) try { const promise = mutex(() => testNameStorage.run(getTestID(test$1), () => _runTest(test$1, parentSkipped))); promise.catch(() => {}); test$1.done = promise; } catch (error) { test$1.fn = () => { throw error; }; } } const _runTest = async (test$1, parentSkipped) => { await dispatch({ name: "test_start", test: test$1 }); const testContext = Object.create(null); const { hasFocusedTests, testNamePattern } = getState(); const isSkipped = parentSkipped || test$1.mode === "skip" || hasFocusedTests && test$1.mode === void 0 || testNamePattern && !testNamePattern.test(getTestID(test$1)); if (isSkipped) { await dispatch({ name: "test_skip", test: test$1 }); return; } if (test$1.mode === "todo") { await dispatch({ name: "test_todo", test: test$1 }); return; } await dispatch({ name: "test_started", test: test$1 }); const { afterEach: afterEach$1, beforeEach: beforeEach$1 } = getEachHooksForTest(test$1); for (const hook of beforeEach$1) { if (test$1.errors.length > 0) break; await _callCircusHook({ hook, test: test$1, testContext }); } await _callCircusTest(test$1, testContext); for (const hook of afterEach$1) await _callCircusHook({ hook, test: test$1, testContext }); await dispatch({ name: "test_done", test: test$1 }); }; const _callCircusHook = async ({ hook, test: test$1, describeBlock, testContext = {} }) => { await dispatch({ hook, name: "hook_start" }); const timeout = hook.timeout || getState().testTimeout; try { await callAsyncCircusFn(hook, testContext, { isHook: true, timeout }); await dispatch({ describeBlock, hook, name: "hook_success", test: test$1 }); } catch (error) { await dispatch({ describeBlock, error, hook, name: "hook_failure", test: test$1 }); } }; const _callCircusTest = async (test$1, testContext) => { await dispatch({ name: "test_fn_start", test: test$1 }); const timeout = test$1.timeout || getState().testTimeout; invariant(test$1.fn, "Tests with no 'fn' should have 'mode' set to 'skipped'"); if (test$1.errors.length > 0) return; try { await callAsyncCircusFn(test$1, testContext, { isHook: false, timeout }); if (test$1.failing) { test$1.asyncError.message = "Failing test passed even though it was supposed to fail. Remove `.failing` to remove error."; await dispatch({ error: test$1.asyncError, name: "test_fn_failure", test: test$1 }); } else await dispatch({ name: "test_fn_success", test: test$1 }); } catch (error) { if (test$1.failing) await dispatch({ name: "test_fn_success", test: test$1 }); else await dispatch({ error, name: "test_fn_failure", test: test$1 }); } }; var run_default = run; //#endregion //#region src/index.ts const describe = (() => { const describe$1 = (blockName, blockFn) => _dispatchDescribe(blockFn, blockName, describe$1); const only = (blockName, blockFn) => _dispatchDescribe(blockFn, blockName, only, "only"); const skip = (blockName, blockFn) => _dispatchDescribe(blockFn, blockName, skip, "skip"); describe$1.each = bind(describe$1, false); only.each = bind(only, false); skip.each = bind(skip, false); describe$1.only = only; describe$1.skip = skip; return describe$1; })(); const _dispatchDescribe = (blockFn, blockName, describeFn, mode) => { const asyncError = new ErrorWithStack(void 0, describeFn); if (blockFn === void 0) { asyncError.message = "Missing second argument. It must be a callback function."; throw asyncError; } if (typeof blockFn !== "function") { asyncError.message = `Invalid second argument, ${blockFn}. It must be a callback function.`; throw asyncError; } try { blockName = convertDescriptorToString(blockName); } catch (error) { asyncError.message = error.message; throw asyncError; } dispatchSync({ asyncError, blockName, mode, name: "start_describe_definition" }); const describeReturn = blockFn(); if (isPromise(describeReturn)) throw new ErrorWithStack("Returning a Promise from \"describe\" is not supported. Tests must be defined synchronously.", describeFn); else if (describeReturn !== void 0) throw new ErrorWithStack("A \"describe\" callback must not return a value.", describeFn); dispatchSync({ blockName, mode, name: "finish_describe_definition" }); }; const _addHook = (fn, hookType, hookFn, timeout) => { const asyncError = new ErrorWithStack(void 0, hookFn); if (typeof fn !== "function") { asyncError.message = "Invalid first argument. It must be a callback function."; throw asyncError; } dispatchSync({ asyncError, fn, hookType, name: "add_hook", timeout }); }; const beforeEach = (fn, timeout) => _addHook(fn, "beforeEach", beforeEach, timeout); const beforeAll = (fn, timeout) => _addHook(fn, "beforeAll", beforeAll, timeout); const afterEach = (fn, timeout) => _addHook(fn, "afterEach", afterEach, timeout); const afterAll = (fn, timeout) => _addHook(fn, "afterAll", afterAll, timeout); const test = (() => { const test$1 = (testName, fn, timeout) => _addTest(testName, void 0, false, fn, test$1, timeout); const skip = (testName, fn, timeout) => _addTest(testName, "skip", false, fn, skip, timeout); const only = (testName, fn, timeout) => _addTest(testName, "only", false, fn, test$1.only, timeout); const concurrentTest = (testName, fn, timeout) => _addTest(testName, void 0, true, fn, concurrentTest, timeout); const concurrentOnly = (testName, fn, timeout) => _addTest(testName, "only", true, fn, concurrentOnly, timeout); const bindFailing = (concurrent, mode) => { const failing = (testName, fn, timeout, eachError) => _addTest(testName, mode, concurrent, fn, failing, timeout, true, eachError); failing.each = bind(failing, false, true); return failing; }; test$1.todo = (testName, ...rest) => { if (rest.length > 0 || typeof testName !== "string") throw new ErrorWithStack("Todo must be called with only a description.", test$1.todo); return _addTest(testName, "todo", false, () => {}, test$1.todo); }; const _addTest = (testName, mode, concurrent, fn, testFn, timeout, failing, asyncError = new ErrorWithStack(void 0, testFn)) => { try { testName = convertDescriptorToString(testName); } catch (error) { asyncError.message = error.message; throw asyncError; } if (fn === void 0) { asyncError.message = "Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder."; throw asyncError; } if (typeof fn !== "function") { asyncError.message = `Invalid second argument, ${fn}. It must be a callback function.`; throw asyncError; } return dispatchSync({ asyncError, concurrent, failing: failing === void 0 ? false : failing, fn, mode, name: "add_test", testName, timeout }); }; test$1.each = bind(test$1); only.each = bind(only); skip.each = bind(skip); concurrentTest.each = bind(concurrentTest, false); concurrentOnly.each = bind(concurrentOnly, false); only.failing = bindFailing(false, "only"); skip.failing = bindFailing(false, "skip"); test$1.failing = bindFailing(false); test$1.only = only; test$1.skip = skip; test$1.concurrent = concurrentTest; concurrentTest.only = concurrentOnly; concurrentTest.skip = skip; concurrentTest.failing = bindFailing(true); concurrentOnly.failing = bindFailing(true, "only"); return test$1; })(); const it = test; var src_default = { afterAll, afterEach, beforeAll, beforeEach, describe, it, test }; //#endregion //#region src/testCaseReportHandler.ts const testCaseReportHandler = (testPath, sendMessageToJest) => (event) => { switch (event.name) { case "test_started": { const testCaseStartInfo = createTestCaseStartInfo(event.test); sendMessageToJest("test-case-start", [testPath, testCaseStartInfo]); break; } case "test_todo": case "test_done": { const testResult = makeSingleTestResult(event.test); const testCaseResult = parseSingleTestResult(testResult); sendMessageToJest("test-case-result", [testPath, testCaseResult]); break; } } }; var testCaseReportHandler_default = testCaseReportHandler; //#endregion //#region src/unhandledRejectionHandler.ts const { setTimeout } = globalThis; const untilNextEventLoopTurn = async () => { return new Promise((resolve) => { setTimeout(resolve, 0); }); }; const unhandledRejectionHandler = (runtime, waitForUnhandledRejections) => { return async (event, state) => { if (event.name === "hook_start") runtime.enterTestCode(); else if (event.name === "hook_success" || event.name === "hook_failure") { runtime.leaveTestCode(); if (waitForUnhandledRejections) await untilNextEventLoopTurn(); const { test: test$1, describeBlock, hook } = event; const { asyncError, type } = hook; if (type === "beforeAll") { invariant(describeBlock, "always present for `*All` hooks"); for (const error of state.unhandledRejectionErrorByPromise.values()) addErrorToEachTestUnderDescribe(describeBlock, error, asyncError); } else if (type === "afterAll") for (const error of state.unhandledRejectionErrorByPromise.values()) state.unhandledErrors.push([error, asyncError]); else { invariant(test$1, "always present for `*Each` hooks"); for (const error of test$1.unhandledRejectionErrorByPromise.values()) test$1.errors.push([error, asyncError]); } } else if (event.name === "test_fn_start") runtime.enterTestCode(); else if (event.name === "test_fn_success" || event.name === "test_fn_failure") { runtime.leaveTestCode(); if (waitForUnhandledRejections) await untilNextEventLoopTurn(); const { test: test$1 } = event; invariant(test$1, "always present for `*Each` hooks"); for (const error of test$1.unhandledRejectionErrorByPromise.values()) test$1.errors.push([error, event.test.asyncError]); } else if (event.name === "teardown") { if (waitForUnhandledRejections) await untilNextEventLoopTurn(); state.unhandledErrors.push(...state.unhandledRejectionErrorByPromise.values()); } }; }; //#endregion //#region src/legacy-code-todo-rewrite/jestAdapterInit.ts const initialize = async ({ config, environment, runtime, globalConfig, localRequire, parentProcess, sendMessageToJest, setGlobalsForRuntime, testPath }) => { if (globalConfig.testTimeout) getState().testTimeout = globalConfig.testTimeout; getState().maxConcurrency = globalConfig.maxConcurrency; getState().randomize = globalConfig.randomize; getState().seed = globalConfig.seed; const globalsObject = { ...src_default, fdescribe: src_default.describe.only, fit: src_default.it.only, xdescribe: src_default.describe.skip, xit: src_default.it.skip, xtest: src_default.it.skip }; addEventHandler(eventHandler); if (environment.handleTestEvent) addEventHandler(environment.handleTestEvent.bind(environment)); jestExpect.setState({ expand: globalConfig.expand }); const runtimeGlobals = { ...globalsObject, expect: jestExpect }; setGlobalsForRuntime(runtimeGlobals); if (config.injectGlobals) Object.assign(environment.global, runtimeGlobals); await dispatch({ name: "setup", parentProcess, runtimeGlobals, testNamePattern: globalConfig.testNamePattern }); if (config.testLocationInResults) await dispatch({ name: "include_test_location_in_result" }); for (const path$1 of [...config.snapshotSerializers].reverse()) addSerializer(localRequire(path$1)); const snapshotResolver = await buildSnapshotResolver(config, localRequire); const snapshotPath = snapshotResolver.resolveSnapshotPath(testPath); const snapshotState = new SnapshotState(snapshotPath, { expand: globalConfig.expand, prettierPath: config.prettierPath, rootDir: config.rootDir, snapshotFormat: config.snapshotFormat, updateSnapshot: globalConfig.updateSnapshot }); jestExpect.setState({ snapshotState, testPath }); addEventHandler(handleSnapshotStateAfterRetry(snapshotState)); if (sendMessageToJest) addEventHandler(testCaseReportHandler_default(testPath, sendMessageToJest)); addEventHandler(unhandledRejectionHandler(runtime, globalConfig.waitForUnhandledRejections)); return { globals: globalsObject, snapshotState }; }; const runAndTransformResultsToJestFormat = async ({ config, globalConfig, setupAfterEnvPerfStats, testPath }) => { const runResult = await run_default(); let numFailingTests = 0; let numPassingTests = 0; let numPendingTests = 0; let numTodoTests = 0; const assertionResults = runResult.testResults.map((testResult) => { let status; if (testResult.status === "skip") { status = "pending"; numPendingTests += 1; } else if (testResult.status === "todo") { status = "todo"; numTodoTests += 1; } else if (testResult.errors.length > 0) { status = "failed"; numFailingTests += 1; } else { status = "passed"; numPassingTests += 1; } const ancestorTitles = testResult.testPath.filter((name) => name !== ROOT_DESCRIBE_BLOCK_NAME); const title = ancestorTitles.pop(); return { ancestorTitles, duration: testResult.duration, failing: testResult.failing, failureDetails: testResult.errorsDetailed, failureMessages: testResult.errors, fullName: title ? [...ancestorTitles, title].join(" ") : ancestorTitles.join(" "), invocations: testResult.invocations, location: testResult.location, numPassingAsserts: testResult.numPassingAsserts, retryReasons: testResult.retryReasons, startAt: testResult.startedAt, status, title: testResult.testPath.at(-1) }; }); let failureMessage = formatResultsErrors(assertionResults, config, globalConfig, testPath); let testExecError; if (runResult.unhandledErrors.length > 0) { testExecError = { message: "", stack: runResult.unhandledErrors.join("\n") }; failureMessage = `${failureMessage || ""}\n\n${runResult.unhandledErrors.map((err) => formatExecError(err, config, globalConfig)).join("\n")}`; } await dispatch({ name: "teardown" }); const emptyTestResult = createEmptyTestResult(); return { ...emptyTestResult, console: void 0, displayName: config.displayName, failureMessage, numFailingTests, numPassingTests, numPendingTests, numTodoTests, perfStats: { ...emptyTestResult.perfStats, ...setupAfterEnvPerfStats }, testExecError, testFilePath: testPath, testResults: assertionResults }; }; const handleSnapshotStateAfterRetry = (snapshotState) => (event) => { switch (event.name) { case "test_retry": snapshotState.clear(); } }; const eventHandler = async (event) => { switch (event.name) { case "test_start": { jestExpect.setState({ currentTestName: getTestID(event.test), testFailing: event.test.failing }); break; } case "test_done": { event.test.numPassingAsserts = jestExpect.getState().numPassingAsserts; _addSuppressedErrors(event.test); _addExpectedAssertionErrors(event.test); break; } } }; const _addExpectedAssertionErrors = (test$1) => { const { isExpectingAssertions } = jestExpect.getState(); const failures = jestExpect.extractExpectedAssertionsErrors(); if (isExpectingAssertions && test$1.errors.length > 0) return; test$1.errors.push(...failures.map((failure) => failure.error)); }; const _addSuppressedErrors = (test$1) => { const { suppressedErrors } = jestExpect.getState(); jestExpect.setState({ suppressedErrors: [] }); if (suppressedErrors.length > 0) test$1.errors.push(...suppressedErrors); }; //#endregion export { eventHandler, initialize, runAndTransformResultsToJestFormat };