- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1249 lines
44 KiB
JavaScript
1249 lines
44 KiB
JavaScript
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 }; |