fix(runtime): resolve expect and @jest/expect from the internal module registry (#16130)

This commit is contained in:
Simen Bekkhus
2026-05-06 09:23:25 +02:00
committed by GitHub
parent 405739da77
commit ab2a0850cf
6 changed files with 100 additions and 0 deletions
+1
View File
@@ -26,6 +26,7 @@
- `[jest-circus, jest-jasmine2]` Include `Error.cause` in JSON `failureMessages` output ([#15949](https://github.com/jestjs/jest/issues/15949))
- `[jest-config]` Fix preset path resolution on Windows when the preset uses subpath `exports` ([#15961](https://github.com/jestjs/jest/pull/15961))
- `[jest-environment-node]` Fix `--localstorage-file` warning on Node 25+ ([#16086](https://github.com/jestjs/jest/pull/16086))
- `[jest-runtime]` Resolve `expect` and `@jest/expect` from the internal module registry so test-file imports share the same `JestAssertionError` as the global `expect` ([#16130](https://github.com/jestjs/jest/pull/16130))
- `[jest-runtime]` Improve CJS-from-ESM interop: `__esModule`/Babel default unwrap, broader named-export coverage, and shared CJS singleton across importers ([#16050](https://github.com/jestjs/jest/pull/16050))
- `[jest-runtime]` Load `.js` files with ESM syntax but no `"type":"module"` marker as native ESM ([#16050](https://github.com/jestjs/jest/pull/16050))
- `[jest-runtime]` Extend the `.js`-with-ESM-syntax fallback to `require()` on Node v24.9+ - falls back to `require(esm)` when the CJS parser rejects ESM syntax ([#16078](https://github.com/jestjs/jest/pull/16078))
@@ -0,0 +1,13 @@
/**
* 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.
*/
import runJest from '../runJest';
test('toThrow(JestAssertionError) passes when imported from a separate require call', () => {
const result = runJest('jest-assertion-error-import');
expect(result.exitCode).toBe(0);
});
@@ -0,0 +1,18 @@
/**
* 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.
*/
'use strict';
// `require('expect')` in a test file must resolve to the same module instance
// that backs the global `expect`, so JestAssertionError is the same class.
const {JestAssertionError} = require('expect');
test('toThrow(JestAssertionError) passes when a matcher assertion fails', () => {
expect(() => {
expect('1').toBe(2);
}).toThrow(JestAssertionError);
});
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
@@ -197,6 +197,58 @@ it('mock dispatch computes moduleID once per requireModuleOrMock call', async ()
getModuleIdSpy.mockRestore();
});
describe.each(['expect', '@jest/expect'])(
'framework singleton module: %s',
moduleName => {
it('requireModuleOrMock returns the internal instance', async () => {
const runtime = await createRuntime(__filename);
const internal = runtime.requireInternalModule(
runtime.__mockRootPath,
moduleName,
);
const user = runtime.requireModuleOrMock(
runtime.__mockRootPath,
moduleName,
);
expect(user).toBe(internal);
});
it('requireActual returns the internal instance', async () => {
const runtime = await createRuntime(__filename);
const internal = runtime.requireInternalModule(
runtime.__mockRootPath,
moduleName,
);
const actual = runtime.requireActual(runtime.__mockRootPath, moduleName);
expect(actual).toBe(internal);
});
it('jest.mock() is respected and does not affect the global expect', async () => {
const runtime = await createRuntime(__filename);
const root = runtime.requireModule(runtime.__mockRootPath);
const mockImpl = {isMock: true};
root.jest.mock(moduleName, () => mockImpl);
const mocked = runtime.requireModuleOrMock(
runtime.__mockRootPath,
moduleName,
);
expect(mocked).toBe(mockImpl);
// The internal instance backing the global must be unaffected.
const internal = runtime.requireInternalModule(
runtime.__mockRootPath,
moduleName,
);
expect(internal).not.toBe(mockImpl);
// requireActual always bypasses the mock and returns the shared instance.
const actual = runtime.requireActual(runtime.__mockRootPath, moduleName);
expect(actual).toBe(internal);
});
},
);
it('unmocks virtual mocks after they have been mocked previously', async () => {
const runtime = await createRuntime(__filename);
const root = runtime.requireModule(runtime.__mockRootPath);
+11
View File
@@ -57,6 +57,11 @@ import type {EnvironmentGlobals} from './internals/types';
// through the VM.
const INTERNAL_MODULE_REQUIRE_OUTSIDE_OPTIMIZED_MODULES = new Set(['chalk']);
// Framework modules that define shared singleton state (e.g. `JestAssertionError`).
// Redirecting user requires to the internal registry ensures test code and the
// framework see the same class instances.
const FRAMEWORK_SINGLETON_MODULES = new Set(['@jest/expect', 'expect']);
const esmIsAvailable = typeof SourceTextModule === 'function';
type HasteMapOptions = {
@@ -400,6 +405,9 @@ export default class Runtime {
}
requireActual<T = unknown>(from: string, moduleName: string): T {
if (FRAMEWORK_SINGLETON_MODULES.has(moduleName)) {
return this.requireInternalModule<T>(from, moduleName);
}
return this.requireModule<T>(from, moduleName, undefined, true);
}
@@ -490,6 +498,9 @@ export default class Runtime {
if (shouldMock) {
return this._requireMockWithId<T>(from, moduleName, moduleID);
}
if (FRAMEWORK_SINGLETON_MODULES.has(moduleName)) {
return this.requireInternalModule<T>(from, moduleName);
}
return this.requireModule<T>(from, moduleName);
} catch (error) {
const moduleNotFound = Resolver.tryCastModuleNotFoundError(error);