Skip to content

Commit 3add7f1

Browse files
committed
Address changes in jest 30.x
1 parent 571fa35 commit 3add7f1

File tree

7 files changed

+92
-41
lines changed

7 files changed

+92
-41
lines changed

build-tests-samples/heft-node-basic-tutorial/src/test/__snapshots__/ExampleTest.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

33
exports[`Example Test Correctly handles snapshots 1`] = `
44
Object {

build-tests-samples/heft-node-rig-tutorial/src/test/__snapshots__/ExampleTest.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

33
exports[`Example Test Correctly handles snapshots 1`] = `
44
Object {

build-tests/api-extractor-test-05/dist/tsdoc-metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"toolPackages": [
66
{
77
"packageName": "@microsoft/api-extractor",
8-
"packageVersion": "7.57.8"
8+
"packageVersion": "7.58.0"
99
}
1010
]
1111
}

build-tests/heft-node-everything-test/src/test/__snapshots__/ExampleTest.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

33
exports[`Example Test Correctly handles snapshots 1`] = `
44
Object {

heft-plugins/heft-jest-plugin/src/JestRealPathPatch.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,10 @@ const jestResolvePackageFolder: string = path.dirname(require.resolve('jest-reso
1010
const jestUtilPackageFolder: string = path.dirname(
1111
require.resolve('jest-util/package.json', { paths: [jestResolvePackageFolder] })
1212
);
13-
const jestUtilTryRealpathPath: string = path.resolve(jestUtilPackageFolder, './build/tryRealpath.js');
1413

1514
const { realNodeModulePath }: RealNodeModulePathResolver = new RealNodeModulePathResolver();
1615

17-
const tryRealpathModule: {
18-
default: (filePath: string) => string;
19-
} = require(jestUtilTryRealpathPath);
20-
tryRealpathModule.default = (input: string): string => {
16+
const customTryRealpath = (input: string): string => {
2117
try {
2218
return realNodeModulePath(input);
2319
} catch (error) {
@@ -30,3 +26,31 @@ tryRealpathModule.default = (input: string): string => {
3026
}
3127
return input;
3228
};
29+
30+
const jestUtilPackageJson: { version: string } = require(path.join(jestUtilPackageFolder, 'package.json'));
31+
const jestUtilMajorVersion: number = parseInt(jestUtilPackageJson.version.split('.')[0], 10);
32+
33+
if (jestUtilMajorVersion >= 30) {
34+
// jest-util 30+: everything is bundled in index.js.
35+
// tryRealpath is exported as a non-configurable getter, so we can't set it directly.
36+
// Instead, replace the require-cache entry with an object that shadows the getter.
37+
const jestUtilIndexPath: string = require.resolve('jest-util', {
38+
paths: [jestResolvePackageFolder]
39+
});
40+
const jestUtilExports: object = require(jestUtilIndexPath);
41+
const patchedExports: object = Object.create(jestUtilExports);
42+
Object.defineProperty(patchedExports, 'tryRealpath', {
43+
value: customTryRealpath,
44+
writable: true,
45+
enumerable: true,
46+
configurable: true
47+
});
48+
require.cache[jestUtilIndexPath]!.exports = patchedExports;
49+
} else {
50+
// jest-util < 30: tryRealpath is a standalone module; replace its default export.
51+
const jestUtilTryRealpathPath: string = path.resolve(jestUtilPackageFolder, './build/tryRealpath.js');
52+
const tryRealpathModule: {
53+
default: (filePath: string) => string;
54+
} = require(jestUtilTryRealpathPath);
55+
tryRealpathModule.default = customTryRealpath;
56+
}

heft-plugins/heft-jest-plugin/src/patches/jestWorkerPatch.ts

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/* eslint-disable no-console */
55

66
import * as path from 'node:path';
7+
import { createRequire } from 'node:module';
78

89
import { Import, FileSystem } from '@rushstack/node-core-library';
910

@@ -62,41 +63,44 @@ function applyPatch(): void {
6263
useNodeJSResolver: true
6364
});
6465

65-
const baseWorkerPoolPath: string = path.join(jestWorkerFolder, 'build/base/BaseWorkerPool.js');
66-
const baseWorkerPoolFilename: string = path.basename(baseWorkerPoolPath); // BaseWorkerPool.js
66+
// jest-worker 30.x switched to a webpack-bundled single-file architecture.
67+
// For 29.x and earlier, patch build/base/BaseWorkerPool.js directly.
68+
// For 30.x and later, patch build/index.js (the webpack bundle).
69+
const jestWorkerPackageJson: { version: string } = require(path.join(jestWorkerFolder, 'package.json'));
70+
const jestWorkerMajorVersion: number = parseInt(jestWorkerPackageJson.version.split('.')[0], 10);
71+
const isBundled: boolean = jestWorkerMajorVersion >= 30;
6772

68-
if (!FileSystem.exists(baseWorkerPoolPath)) {
73+
const targetPath: string = isBundled
74+
? path.join(jestWorkerFolder, 'build/index.js')
75+
: path.join(jestWorkerFolder, 'build/base/BaseWorkerPool.js');
76+
77+
if (!FileSystem.exists(targetPath)) {
6978
throw new Error(
70-
'The BaseWorkerPool.js file was not found in the expected location:\n' + baseWorkerPoolPath
79+
`The ${path.basename(targetPath)} file was not found in the expected location:\n` + targetPath
7180
);
7281
}
7382

7483
// Load the module
75-
const baseWorkerPoolModule: IBaseWorkerPoolModule = require(baseWorkerPoolPath);
84+
const targetModule: IBaseWorkerPoolModule = require(targetPath);
7685

7786
// Obtain the metadata for the module
78-
let baseWorkerPoolModuleMetadata: NodeModule | undefined = undefined;
87+
let targetModuleMetadata: NodeModule | undefined = undefined;
7988
for (const childModule of module.children) {
80-
if (path.basename(childModule.filename || '').toUpperCase() === baseWorkerPoolFilename.toUpperCase()) {
81-
if (baseWorkerPoolModuleMetadata) {
89+
// Match by full path to avoid false positives (e.g. many modules named index.js)
90+
if (childModule.filename === targetPath) {
91+
if (targetModuleMetadata) {
8292
throw new Error('More than one child module matched while detecting Node.js module metadata');
8393
}
84-
baseWorkerPoolModuleMetadata = childModule;
94+
targetModuleMetadata = childModule;
8595
}
8696
}
8797

88-
if (!baseWorkerPoolModuleMetadata) {
89-
throw new Error('Failed to detect the Node.js module metadata for BaseWorkerPool.js');
98+
if (!targetModuleMetadata) {
99+
throw new Error(`Failed to detect the Node.js module metadata for ${path.basename(targetPath)}`);
90100
}
91101

92102
// Load the original file contents
93-
const originalFileContent: string = FileSystem.readFile(baseWorkerPoolPath);
94-
95-
// Add boilerplate so that eval() will return the exports
96-
let patchedCode: string =
97-
'// PATCHED BY HEFT USING eval()\n\nexports = {}\n' +
98-
originalFileContent +
99-
'\n// return value:\nexports';
103+
const originalFileContent: string = FileSystem.readFile(targetPath);
100104

101105
// Apply the patch. We will replace this:
102106
//
@@ -106,7 +110,7 @@ function applyPatch(): void {
106110
//
107111
// const FORCE_EXIT_DELAY = 7000;
108112
let matched: boolean = false;
109-
patchedCode = patchedCode.replace(
113+
const patchedCode: string = originalFileContent.replace(
110114
/(const\s+FORCE_EXIT_DELAY\s*=\s*)(\d+)(\s*\;)/,
111115
(matchedString: string, leftPart: string, middlePart: string, rightPart: string): string => {
112116
matched = true;
@@ -115,24 +119,47 @@ function applyPatch(): void {
115119
);
116120

117121
if (!matched) {
118-
throw new Error('The expected pattern was not found in the file:\n' + baseWorkerPoolPath);
122+
throw new Error('The expected pattern was not found in the file:\n' + targetPath);
119123
}
120124

121-
function evalInContext(): IBaseWorkerPoolModule {
122-
// Remap the require() function for the eval() context
125+
if (isBundled) {
126+
// jest-worker 30.x: webpack bundle uses `module.exports = __webpack_exports__` at the top level.
127+
// Shadow `module` with a shim so that assignment writes to our object, then copy the
128+
// resulting exports over the already-cached module exports in-place.
129+
function evalInContextBundled(): Record<string, unknown> {
130+
// createRequire(targetPath) produces a proper require function with resolve/cache/etc.
131+
// and the right module-resolution context (resolves relative to jest-worker's build dir).
132+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
133+
const require: NodeRequire = createRequire(targetPath);
134+
// Shadow `module` so the bundle's `module.exports = ...` writes to our shim
135+
const module: { exports: Record<string, unknown> } = { exports: {} };
136+
// eslint-disable-next-line no-eval
137+
eval(patchedCode);
138+
return module.exports;
139+
}
123140

124-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
125-
function require(modulePath: string): void {
126-
return baseWorkerPoolModuleMetadata!.require(modulePath);
141+
const patchedExports: Record<string, unknown> = evalInContextBundled();
142+
// Can't mutate the cached exports in-place (webpack defines them as read-only getters).
143+
// Replace the exports object in the require cache entirely so future require() calls
144+
// return the patched version.
145+
require.cache[targetModuleMetadata.filename]!.exports = patchedExports;
146+
} else {
147+
// jest-worker < 30: BaseWorkerPool.js uses bare `exports`, wrap for eval return value
148+
const wrappedCode: string =
149+
'// PATCHED BY HEFT USING eval()\n\nexports = {}\n' + patchedCode + '\n// return value:\nexports';
150+
151+
function evalInContext(): IBaseWorkerPoolModule {
152+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
153+
function require(modulePath: string): void {
154+
return targetModuleMetadata!.require(modulePath);
155+
}
156+
// eslint-disable-next-line no-eval
157+
return eval(wrappedCode);
127158
}
128159

129-
// eslint-disable-next-line no-eval
130-
return eval(patchedCode);
160+
const patchedModule: IBaseWorkerPoolModule = evalInContext();
161+
targetModule.default = patchedModule.default;
131162
}
132-
133-
const patchedModule: IBaseWorkerPoolModule = evalInContext();
134-
135-
baseWorkerPoolModule.default = patchedModule.default;
136163
} catch (e) {
137164
console.error();
138165
console.error(`ERROR: ${patchName} failed to patch the "jest-worker" package:`);

libraries/typings-generator/src/test/__snapshots__/StringValuesTypingsGenerator.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

33
exports[`StringValuesTypingsGenerator default exports overrides for individual files with exportAsDefault filled overriding with { exportAsDefault: false } should generate typings 1`] = `
44
Object {

0 commit comments

Comments
 (0)