mirror of
https://github.com/hanzoai/jest.git
synced 2026-09-24 16:59:57 +00:00
fix(jest-runtime): handle function module.exports for CJS-from-ESM named exports (#16150)
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
## main
|
||||
|
||||
## 30.4.2
|
||||
|
||||
### Fixes
|
||||
|
||||
- `[jest-runtime]` Fix named imports from CJS modules whose `module.exports` is a function with own-property exports ([#16150](https://github.com/jestjs/jest/pull/16150))
|
||||
|
||||
## 30.4.1
|
||||
|
||||
### Features
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`runs ESM/CJS interop fixture (__esModule unwrapping, named exports, singleton caching) 1`] = `
|
||||
"Test Suites: 1 passed, 1 total
|
||||
Tests: 6 passed, 6 total
|
||||
Tests: 8 passed, 8 total
|
||||
Snapshots: 0 total
|
||||
Time: <<REPLACED>>
|
||||
Ran all test suites matching cjs-esm-interop."
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
import greet, {helper} from '../babel-style-default.cjs';
|
||||
import plainDefault, {multiply, value} from '../plain-cjs.cjs';
|
||||
import fnDefault, {
|
||||
helper as fnHelper,
|
||||
splitCookiesString,
|
||||
} from '../fn-exports.cjs';
|
||||
import {getCount as countA, increment as incA} from '../importer-a.mjs';
|
||||
import {getCount as countB, increment as incB} from '../importer-b.mjs';
|
||||
|
||||
@@ -38,6 +42,18 @@ test('named imports from plain CJS work', () => {
|
||||
expect(multiply(6, 7)).toBe(42);
|
||||
});
|
||||
|
||||
// ── CJS with function module.exports ─────────────────────────────────────────
|
||||
|
||||
test('default import of function-export CJS is the function itself', () => {
|
||||
expect(typeof fnDefault).toBe('function');
|
||||
expect(fnDefault('x')).toEqual({input: 'x'});
|
||||
});
|
||||
|
||||
test('named imports from function-export CJS work', () => {
|
||||
expect(splitCookiesString('a,b')).toEqual(['a', 'b']);
|
||||
expect(fnHelper(21)).toBe(42);
|
||||
});
|
||||
|
||||
// ── CJS-as-ESM caching (singleton) ───────────────────────────────────────────
|
||||
|
||||
test('two ESM importers of the same CJS module share one singleton instance', () => {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// CJS pattern where module.exports is a function with own-property named exports.
|
||||
'use strict';
|
||||
|
||||
function parse(input) {
|
||||
return {input};
|
||||
}
|
||||
|
||||
parse.splitCookiesString = function splitCookiesString(str) {
|
||||
return str.split(',');
|
||||
};
|
||||
|
||||
parse.helper = function helper(x) {
|
||||
return x * 2;
|
||||
};
|
||||
|
||||
module.exports = parse;
|
||||
@@ -673,13 +673,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip-address@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "ip-address@npm:9.0.5"
|
||||
dependencies:
|
||||
jsbn: "npm:1.1.0"
|
||||
sprintf-js: "npm:^1.1.3"
|
||||
checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c
|
||||
"ip-address@npm:^10.1.1":
|
||||
version: 10.2.0
|
||||
resolution: "ip-address@npm:10.2.0"
|
||||
checksum: 10/12fec399e1af5753ac322e47a6d81a50d3a528b3abb17c09525b2a2edcaedcca628c40520706f7037bc4d8e951b0296c47e7b86d0a8e6e2335c8f0ba4afcfac1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -734,13 +731,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsbn@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "jsbn@npm:1.1.0"
|
||||
checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.snakecase@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "lodash.snakecase@npm:4.1.1"
|
||||
@@ -1217,19 +1207,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"socks@npm:^2.8.3":
|
||||
version: 2.8.4
|
||||
resolution: "socks@npm:2.8.4"
|
||||
version: 2.8.9
|
||||
resolution: "socks@npm:2.8.9"
|
||||
dependencies:
|
||||
ip-address: "npm:^9.0.5"
|
||||
ip-address: "npm:^10.1.1"
|
||||
smart-buffer: "npm:^4.2.0"
|
||||
checksum: 10/ab3af97aeb162f32c80e176c717ccf16a11a6ebb4656a62b94c0f96495ea2a1f4a8206c04b54438558485d83d0c5f61920c07a1a5d3963892a589b40cc6107dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sprintf-js@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "sprintf-js@npm:1.1.3"
|
||||
checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb
|
||||
checksum: 10/8675e6b023faeaa5c2511664b106fd21f5d5ecb403584412e0efae58a76e0c0661c0c18ca340988f6cb398232308dae85fb710f847c9c6e0a6af33b07e4cd33a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -130,6 +130,27 @@ describe('syntheticBuilders', () => {
|
||||
},
|
||||
);
|
||||
|
||||
testWithVmEsm(
|
||||
'exposes own-property named exports when module.exports is a function',
|
||||
async () => {
|
||||
// Pattern: `module.exports = function parse() {}; parse.helper = ...`
|
||||
function parse() {}
|
||||
(parse as any).helper = () => 'h';
|
||||
(parse as any).splitCookiesString = () => ['a', 'b'];
|
||||
const m = buildCjsAsEsmSyntheticModule(
|
||||
'/from.js',
|
||||
'/mod.cjs',
|
||||
context(),
|
||||
() => parse,
|
||||
makeCache(['helper', 'splitCookiesString']),
|
||||
);
|
||||
const ns = await evaluate(m);
|
||||
expect(ns.default).toBe(parse);
|
||||
expect(ns.helper).toBe((parse as any).helper);
|
||||
expect(ns.splitCookiesString).toBe((parse as any).splitCookiesString);
|
||||
},
|
||||
);
|
||||
|
||||
testWithVmEsm('handles non-object module.exports', async () => {
|
||||
const m = buildCjsAsEsmSyntheticModule(
|
||||
'/from.js',
|
||||
|
||||
@@ -114,9 +114,10 @@ export function buildCjsAsEsmSyntheticModule(
|
||||
const parsedExports = cjsExportsCache.getExportsOf(from, modulePath);
|
||||
|
||||
// CJS modules can legally set `module.exports` to `null` or a primitive.
|
||||
// Functions are also valid (e.g. `module.exports = fn; fn.helper = ...`).
|
||||
const cjsRecord =
|
||||
typeof cjs === 'object' && cjs !== null
|
||||
? (cjs as Record<string, unknown>)
|
||||
cjs !== null && (typeof cjs === 'object' || typeof cjs === 'function')
|
||||
? cjs
|
||||
: null;
|
||||
|
||||
const allCandidates = new Set([
|
||||
@@ -126,19 +127,19 @@ export function buildCjsAsEsmSyntheticModule(
|
||||
|
||||
const cjsExports = [...allCandidates].filter(exportName => {
|
||||
// `default` is handled separately below as the whole module.exports.
|
||||
if (exportName === 'default') {
|
||||
if (exportName === 'default' || cjsRecord == null) {
|
||||
return false;
|
||||
}
|
||||
return cjsRecord
|
||||
? Object.hasOwnProperty.call(cjsRecord, exportName)
|
||||
: false;
|
||||
return Object.hasOwn(cjsRecord, exportName);
|
||||
});
|
||||
|
||||
return new SyntheticModule(
|
||||
[...cjsExports, 'default'],
|
||||
function () {
|
||||
for (const exportName of cjsExports) {
|
||||
this.setExport(exportName, (cjs as any)[exportName]);
|
||||
if (cjsRecord != null) {
|
||||
for (const exportName of cjsExports) {
|
||||
this.setExport(exportName, Reflect.get(cjsRecord, exportName));
|
||||
}
|
||||
}
|
||||
// module.exports is the ESM default, matching Node's CJS-from-ESM behavior.
|
||||
// __esModule is not honored — see Node docs on named exports from CJS.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// needed for WebAssembly, see https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/826
|
||||
"lib": ["dom"],
|
||||
"lib": ["es2023", "dom"],
|
||||
"rootDir": "src",
|
||||
"outDir": "build"
|
||||
},
|
||||
|
||||
@@ -13622,13 +13622,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip-address@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "ip-address@npm:9.0.5"
|
||||
dependencies:
|
||||
jsbn: "npm:1.1.0"
|
||||
sprintf-js: "npm:^1.1.3"
|
||||
checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c
|
||||
"ip-address@npm:^10.1.1":
|
||||
version: 10.2.0
|
||||
resolution: "ip-address@npm:10.2.0"
|
||||
checksum: 10/12fec399e1af5753ac322e47a6d81a50d3a528b3abb17c09525b2a2edcaedcca628c40520706f7037bc4d8e951b0296c47e7b86d0a8e6e2335c8f0ba4afcfac1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -15100,13 +15097,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsbn@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "jsbn@npm:1.1.0"
|
||||
checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsc-safe-url@npm:^0.2.2":
|
||||
version: 0.2.4
|
||||
resolution: "jsc-safe-url@npm:0.2.4"
|
||||
@@ -21119,12 +21109,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"socks@npm:^2.8.3":
|
||||
version: 2.8.4
|
||||
resolution: "socks@npm:2.8.4"
|
||||
version: 2.8.9
|
||||
resolution: "socks@npm:2.8.9"
|
||||
dependencies:
|
||||
ip-address: "npm:^9.0.5"
|
||||
ip-address: "npm:^10.1.1"
|
||||
smart-buffer: "npm:^4.2.0"
|
||||
checksum: 10/ab3af97aeb162f32c80e176c717ccf16a11a6ebb4656a62b94c0f96495ea2a1f4a8206c04b54438558485d83d0c5f61920c07a1a5d3963892a589b40cc6107dd
|
||||
checksum: 10/8675e6b023faeaa5c2511664b106fd21f5d5ecb403584412e0efae58a76e0c0661c0c18ca340988f6cb398232308dae85fb710f847c9c6e0a6af33b07e4cd33a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -21279,13 +21269,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sprintf-js@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "sprintf-js@npm:1.1.3"
|
||||
checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sprintf-js@npm:~1.0.2":
|
||||
version: 1.0.3
|
||||
resolution: "sprintf-js@npm:1.0.3"
|
||||
|
||||
Reference in New Issue
Block a user