Skip to content

Commit 3371d13

Browse files
committed
fix(jest): preserve custom environment handleTestEvent
1 parent 47331fb commit 3371d13

3 files changed

Lines changed: 123 additions & 4 deletions

File tree

packages/allure-jest/src/environmentFactory.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { Circus } from "@jest/types";
66
import * as allure from "allure-js-commons";
77
import { Stage, Status, type StatusDetails, type TestResult } from "allure-js-commons";
88
import { type RuntimeMessage, type TestPlanV1, serialize } from "allure-js-commons/sdk";
9-
import { extractMetadataFromString, getMessageAndTraceFromError, getStatusFromError } from "allure-js-commons/sdk";
9+
import { extractMetadataFromString, getMessageAndTraceFromError, getStatusFromError, isPromise } from "allure-js-commons/sdk";
1010
import {
1111
ReporterRuntime,
1212
createDefaultWriter,
@@ -45,6 +45,30 @@ const createJestEnvironment = <T extends typeof JestEnvironment>(Base: T): T =>
4545
constructor(config: AllureJestConfig | AllureJestProjectConfig, context: EnvironmentContext) {
4646
super(config as JestEnvironmentConfig, context);
4747

48+
const handleTestEvent = this.handleTestEvent?.bind(this) as
49+
| ((event: Circus.Event, state: Circus.State) => void | PromiseLike<void>)
50+
| undefined;
51+
52+
// Preserve any event handler defined by the base or custom environment.
53+
this.handleTestEvent = (event: Circus.Event, state: Circus.State) => {
54+
const handleAllureEvent = () => this.#handleTestEvent(event, state);
55+
56+
// Keep Allure's lifecycle in sync even if the custom environment fails,
57+
// then rethrow the original error so Jest keeps its native behavior.
58+
try {
59+
const result = handleTestEvent?.(event, state);
60+
61+
if (isPromise(result)) {
62+
return Promise.resolve(result).finally(handleAllureEvent);
63+
}
64+
} catch (error) {
65+
handleAllureEvent();
66+
throw error;
67+
}
68+
69+
handleAllureEvent();
70+
};
71+
4872
const projectConfig = "projectConfig" in config ? config.projectConfig : config;
4973
const { resultsDir, ...restConfig } = projectConfig?.testEnvironmentOptions || {};
5074

@@ -81,7 +105,7 @@ const createJestEnvironment = <T extends typeof JestEnvironment>(Base: T): T =>
81105
}
82106
}
83107

84-
handleTestEvent = (event: Circus.Event) => {
108+
#handleTestEvent(event: Circus.Event, state: Circus.State) {
85109
switch (event.name) {
86110
case "hook_start":
87111
this.#handleHookStart(event.hook);
@@ -125,7 +149,7 @@ const createJestEnvironment = <T extends typeof JestEnvironment>(Base: T): T =>
125149
default:
126150
break;
127151
}
128-
};
152+
}
129153

130154
#getTestFullName(test: Circus.TestEntry, testTitle: string = test.name) {
131155
const newTestSuitePath = getTestPath(test.parent);

packages/allure-jest/test/spec/simple.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,95 @@ it("should set full name", async () => {
4444
]),
4545
);
4646
});
47+
48+
it("preserves async handleTestEvent from a custom environment", async () => {
49+
const { tests } = await runJestInlineTest({
50+
"jest.config.js": () => `
51+
const config = {
52+
bail: false,
53+
testEnvironment: "./custom-environment.js",
54+
};
55+
56+
module.exports = config;
57+
`,
58+
"custom-environment.js": ({ allureJestFactoryPath }) => `
59+
const { TestEnvironment } = require("jest-environment-node");
60+
const { createJestEnvironment } = require("${allureJestFactoryPath}");
61+
62+
class CustomEnvironment extends TestEnvironment {
63+
async handleTestEvent(event) {
64+
if (event.name === "test_fn_failure") {
65+
this.global.__failedTestNames = [...(this.global.__failedTestNames ?? []), event.test.name];
66+
}
67+
}
68+
}
69+
70+
module.exports = createJestEnvironment(CustomEnvironment);
71+
`,
72+
"sample.spec.js": `
73+
it("fails", () => {
74+
throw new Error("boom");
75+
});
76+
77+
it("observes the custom environment event", () => {
78+
expect(global.__failedTestNames).toEqual(["fails"]);
79+
});
80+
`,
81+
});
82+
83+
expect(tests).toHaveLength(2);
84+
expect(tests).toEqual(
85+
expect.arrayContaining([
86+
expect.objectContaining({
87+
name: "fails",
88+
status: Status.BROKEN,
89+
}),
90+
expect.objectContaining({
91+
name: "observes the custom environment event",
92+
status: Status.PASSED,
93+
}),
94+
]),
95+
);
96+
});
97+
98+
it("preserves sync handleTestEvent from a custom environment", async () => {
99+
const { tests } = await runJestInlineTest({
100+
"jest.config.js": () => `
101+
const config = {
102+
bail: false,
103+
testEnvironment: "./custom-environment.js",
104+
};
105+
106+
module.exports = config;
107+
`,
108+
"custom-environment.js": ({ allureJestFactoryPath }) => `
109+
const { TestEnvironment } = require("jest-environment-node");
110+
const { createJestEnvironment } = require("${allureJestFactoryPath}");
111+
112+
class CustomEnvironment extends TestEnvironment {
113+
handleTestEvent(event) {
114+
if (event.name === "test_start") {
115+
this.global.__startedTestNames = [...(this.global.__startedTestNames ?? []), event.test.name];
116+
}
117+
}
118+
}
119+
120+
module.exports = createJestEnvironment(CustomEnvironment);
121+
`,
122+
"sample.spec.js": `
123+
it("runs first", () => {
124+
expect(global.__startedTestNames).toEqual(["runs first"]);
125+
});
126+
`,
127+
});
128+
129+
expect(tests).toHaveLength(1);
130+
expect(tests).toEqual(
131+
expect.arrayContaining([
132+
expect.objectContaining({
133+
name: "runs first",
134+
status: Status.PASSED,
135+
}),
136+
]),
137+
);
138+
});

packages/allure-jest/test/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { attachment, step } from "allure-js-commons";
77
import type { AllureResults } from "allure-js-commons/sdk";
88
import { MessageReader, getPosixPath } from "allure-js-commons/sdk/reporter";
99

10-
type TestFileWriter = (opts: { allureJestNodePath: string }) => string;
10+
type TestFileWriter = (opts: { allureJestFactoryPath: string; allureJestNodePath: string }) => string;
1111

1212
type TestFiles = Record<string, string | TestFileWriter>;
1313

@@ -31,7 +31,9 @@ export const runJestInlineTest = async (
3131
const testDir = join(__dirname, "fixtures", randomUUID());
3232
const configFileName = "jest.config.js";
3333
const configFilePath = join(testDir, configFileName);
34+
const allureJestFactory = require.resolve("allure-jest/factory");
3435
const allureJestNode = require.resolve("allure-jest/node");
36+
const allureJestFactoryPath = getPosixPath(relative(testDir, allureJestFactory));
3537
const allureJestNodePath = getPosixPath(relative(testDir, allureJestNode));
3638
const testFilesToWrite: TestFiles = {
3739
[configFileName]: `
@@ -77,6 +79,7 @@ export const runJestInlineTest = async (
7779
testFileContent = testFilesToWrite[testFile] as string;
7880
} else {
7981
testFileContent = (testFilesToWrite[testFile] as TestFileWriter)({
82+
allureJestFactoryPath,
8083
allureJestNodePath,
8184
});
8285
}

0 commit comments

Comments
 (0)