From cd8fb03079e4efd2daba88a9989b85b330eae922 Mon Sep 17 00:00:00 2001 From: Dmitrii Evreinov Date: Fri, 1 May 2026 15:06:00 +0400 Subject: [PATCH] fix(jest): preserve custom environment handleTestEvent --- .../allure-jest/src/environmentFactory.ts | 35 ++++++- packages/allure-jest/test/spec/simple.test.ts | 92 +++++++++++++++++++ packages/allure-jest/test/utils.ts | 5 +- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/packages/allure-jest/src/environmentFactory.ts b/packages/allure-jest/src/environmentFactory.ts index 29b325cc1..1817d8830 100644 --- a/packages/allure-jest/src/environmentFactory.ts +++ b/packages/allure-jest/src/environmentFactory.ts @@ -6,7 +6,12 @@ import type { Circus } from "@jest/types"; import * as allure from "allure-js-commons"; import { Stage, Status, type StatusDetails, type TestResult } from "allure-js-commons"; import { type RuntimeMessage, type TestPlanV1, serialize } from "allure-js-commons/sdk"; -import { extractMetadataFromString, getMessageAndTraceFromError, getStatusFromError } from "allure-js-commons/sdk"; +import { + extractMetadataFromString, + getMessageAndTraceFromError, + getStatusFromError, + isPromise, +} from "allure-js-commons/sdk"; import { ReporterRuntime, createDefaultWriter, @@ -45,6 +50,30 @@ const createJestEnvironment = (Base: T): T => constructor(config: AllureJestConfig | AllureJestProjectConfig, context: EnvironmentContext) { super(config as JestEnvironmentConfig, context); + const handleTestEvent = this.handleTestEvent?.bind(this) as + | ((event: Circus.Event, state: Circus.State) => void | PromiseLike) + | undefined; + + // Preserve any event handler defined by the base or custom environment. + this.handleTestEvent = (event: Circus.Event, state: Circus.State) => { + const handleAllureEvent = () => this.#handleTestEvent(event, state); + + // Keep Allure's lifecycle in sync even if the custom environment fails, + // then rethrow the original error so Jest keeps its native behavior. + try { + const result = handleTestEvent?.(event, state); + + if (isPromise(result)) { + return Promise.resolve(result).finally(handleAllureEvent); + } + } catch (error) { + handleAllureEvent(); + throw error; + } + + handleAllureEvent(); + }; + const projectConfig = "projectConfig" in config ? config.projectConfig : config; const { resultsDir, ...restConfig } = projectConfig?.testEnvironmentOptions || {}; @@ -81,7 +110,7 @@ const createJestEnvironment = (Base: T): T => } } - handleTestEvent = (event: Circus.Event) => { + #handleTestEvent(event: Circus.Event, state: Circus.State) { switch (event.name) { case "hook_start": this.#handleHookStart(event.hook); @@ -125,7 +154,7 @@ const createJestEnvironment = (Base: T): T => default: break; } - }; + } #getTestFullName(test: Circus.TestEntry, testTitle: string = test.name) { const newTestSuitePath = getTestPath(test.parent); diff --git a/packages/allure-jest/test/spec/simple.test.ts b/packages/allure-jest/test/spec/simple.test.ts index c7fcffcf3..8d8b3a60c 100644 --- a/packages/allure-jest/test/spec/simple.test.ts +++ b/packages/allure-jest/test/spec/simple.test.ts @@ -44,3 +44,95 @@ it("should set full name", async () => { ]), ); }); + +it("preserves async handleTestEvent from a custom environment", async () => { + const { tests } = await runJestInlineTest({ + "jest.config.js": () => ` + const config = { + bail: false, + testEnvironment: "./custom-environment.js", + }; + + module.exports = config; + `, + "custom-environment.js": ({ allureJestFactoryPath }) => ` + const { TestEnvironment } = require("jest-environment-node"); + const { createJestEnvironment } = require("${allureJestFactoryPath}"); + + class CustomEnvironment extends TestEnvironment { + async handleTestEvent(event) { + if (event.name === "test_fn_failure") { + this.global.__failedTestNames = [...(this.global.__failedTestNames ?? []), event.test.name]; + } + } + } + + module.exports = createJestEnvironment(CustomEnvironment); + `, + "sample.spec.js": ` + it("fails", () => { + throw new Error("boom"); + }); + + it("observes the custom environment event", () => { + expect(global.__failedTestNames).toEqual(["fails"]); + }); + `, + }); + + expect(tests).toHaveLength(2); + expect(tests).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "fails", + status: Status.BROKEN, + }), + expect.objectContaining({ + name: "observes the custom environment event", + status: Status.PASSED, + }), + ]), + ); +}); + +it("preserves sync handleTestEvent from a custom environment", async () => { + const { tests } = await runJestInlineTest({ + "jest.config.js": () => ` + const config = { + bail: false, + testEnvironment: "./custom-environment.js", + }; + + module.exports = config; + `, + "custom-environment.js": ({ allureJestFactoryPath }) => ` + const { TestEnvironment } = require("jest-environment-node"); + const { createJestEnvironment } = require("${allureJestFactoryPath}"); + + class CustomEnvironment extends TestEnvironment { + handleTestEvent(event) { + if (event.name === "test_start") { + this.global.__startedTestNames = [...(this.global.__startedTestNames ?? []), event.test.name]; + } + } + } + + module.exports = createJestEnvironment(CustomEnvironment); + `, + "sample.spec.js": ` + it("runs first", () => { + expect(global.__startedTestNames).toEqual(["runs first"]); + }); + `, + }); + + expect(tests).toHaveLength(1); + expect(tests).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "runs first", + status: Status.PASSED, + }), + ]), + ); +}); diff --git a/packages/allure-jest/test/utils.ts b/packages/allure-jest/test/utils.ts index 69afc76b3..ba94919d5 100644 --- a/packages/allure-jest/test/utils.ts +++ b/packages/allure-jest/test/utils.ts @@ -7,7 +7,7 @@ import { attachment, step } from "allure-js-commons"; import type { AllureResults } from "allure-js-commons/sdk"; import { MessageReader, getPosixPath } from "allure-js-commons/sdk/reporter"; -type TestFileWriter = (opts: { allureJestNodePath: string }) => string; +type TestFileWriter = (opts: { allureJestFactoryPath: string; allureJestNodePath: string }) => string; type TestFiles = Record; @@ -31,7 +31,9 @@ export const runJestInlineTest = async ( const testDir = join(__dirname, "fixtures", randomUUID()); const configFileName = "jest.config.js"; const configFilePath = join(testDir, configFileName); + const allureJestFactory = require.resolve("allure-jest/factory"); const allureJestNode = require.resolve("allure-jest/node"); + const allureJestFactoryPath = getPosixPath(relative(testDir, allureJestFactory)); const allureJestNodePath = getPosixPath(relative(testDir, allureJestNode)); const testFilesToWrite: TestFiles = { [configFileName]: ` @@ -77,6 +79,7 @@ export const runJestInlineTest = async ( testFileContent = testFilesToWrite[testFile] as string; } else { testFileContent = (testFilesToWrite[testFile] as TestFileWriter)({ + allureJestFactoryPath, allureJestNodePath, }); }