diff --git a/CHANGELOG.md b/CHANGELOG.md index cd34d1360be7..155d2285015a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ - [**BREAKING**] `--testPathPattern` is now `--testPathPatterns` - [**BREAKING**] Specifying `testPathPatterns` when programmatically calling `watch` must be specified as `new TestPathPatterns(patterns)`, where `TestPathPatterns` can be imported from `@jest/pattern` - `[jest-reporters, jest-runner]` Unhandled errors without stack get correctly logged to console ([#14619](https://github.com/jestjs/jest/pull/14619)) +- `[jest-util]` Always load `mjs` files with `import` ([#15447](https://github.com/jestjs/jest/pull/15447)) - `[jest-worker]` Properly handle a circular reference error when worker tries to send an assertion fails where either the expected or actual value is circular ([#15191](https://github.com/jestjs/jest/pull/15191)) - `[jest-worker]` Properly handle a BigInt when worker tries to send an assertion fails where either the expected or actual value is BigInt ([#15191](https://github.com/jestjs/jest/pull/15191)) diff --git a/e2e/esm-config/js/jest.config.js b/e2e/esm-config/js/jest.config.js index 22cb6befbeba..e9a929aed86a 100644 --- a/e2e/esm-config/js/jest.config.js +++ b/e2e/esm-config/js/jest.config.js @@ -5,7 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +const displayName = await Promise.resolve('Config from js file'); + export default { - displayName: 'Config from js file', + displayName, testEnvironment: 'node', }; diff --git a/e2e/esm-config/mjs/jest.config.mjs b/e2e/esm-config/mjs/jest.config.mjs index 4e255c33887d..a426783bd541 100644 --- a/e2e/esm-config/mjs/jest.config.mjs +++ b/e2e/esm-config/mjs/jest.config.mjs @@ -5,7 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +const displayName = await Promise.resolve('Config from mjs file'); + export default { - displayName: 'Config from mjs file', + displayName, testEnvironment: 'node', }; diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index 9e9cb86d8d5a..0300d94e27c2 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -26,6 +26,23 @@ jest ...realFs, statSync: () => ({isDirectory: () => true}), }; + }) + .mock('jest-util', () => { + const realUtil = + jest.requireActual('jest-util'); + + return { + ...realUtil, + requireOrImportModule: (filePath: string, interop = true) => { + const result = require(filePath); + + if (interop) { + return realUtil.interopRequireDefault(result).default; + } + + return result; + }, + }; }); let root: string; diff --git a/packages/jest-util/src/requireOrImportModule.ts b/packages/jest-util/src/requireOrImportModule.ts index 7124c8de95ae..84ea040a0bd4 100644 --- a/packages/jest-util/src/requireOrImportModule.ts +++ b/packages/jest-util/src/requireOrImportModule.ts @@ -9,6 +9,39 @@ import {isAbsolute} from 'path'; import {pathToFileURL} from 'url'; import interopRequireDefault from './interopRequireDefault'; +async function importModule( + filePath: string, + applyInteropRequireDefault: boolean, +) { + try { + const moduleUrl = pathToFileURL(filePath); + + // node `import()` supports URL, but TypeScript doesn't know that + const importedModule = await import( + /* webpackIgnore: true */ moduleUrl.href + ); + + if (!applyInteropRequireDefault) { + return importedModule; + } + + if (!importedModule.default) { + throw new Error( + `Jest: Failed to load ESM at ${filePath} - did you use a default export?`, + ); + } + + return importedModule.default; + } catch (error: any) { + if (error.message === 'Not supported') { + throw new Error( + `Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`, + ); + } + throw error; + } +} + export default async function requireOrImportModule( filePath: string, applyInteropRequireDefault = true, @@ -19,40 +52,21 @@ export default async function requireOrImportModule( ); } try { + if (filePath.endsWith('.mjs')) { + return importModule(filePath, applyInteropRequireDefault); + } + const requiredModule = require(filePath); if (!applyInteropRequireDefault) { return requiredModule; } return interopRequireDefault(requiredModule).default; } catch (error: any) { - if (error.code === 'ERR_REQUIRE_ESM') { - try { - const moduleUrl = pathToFileURL(filePath); - - // node `import()` supports URL, but TypeScript doesn't know that - const importedModule = await import( - /* webpackIgnore: true */ moduleUrl.href - ); - - if (!applyInteropRequireDefault) { - return importedModule; - } - - if (!importedModule.default) { - throw new Error( - `Jest: Failed to load ESM at ${filePath} - did you use a default export?`, - ); - } - - return importedModule.default; - } catch (innerError: any) { - if (innerError.message === 'Not supported') { - throw new Error( - `Jest: Your version of Node does not support dynamic import - please enable it or use a .cjs file extension for file ${filePath}`, - ); - } - throw innerError; - } + if ( + error.code === 'ERR_REQUIRE_ESM' || + error.code === 'ERR_REQUIRE_ASYNC_MODULE' + ) { + return importModule(filePath, applyInteropRequireDefault); } else { throw error; } diff --git a/yarn.lock b/yarn.lock index cc8fe5240380..5dbf79e7c3c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10555,8 +10555,8 @@ __metadata: linkType: hard "express@npm:^4.17.3": - version: 4.21.1 - resolution: "express@npm:4.21.1" + version: 4.21.2 + resolution: "express@npm:4.21.2" dependencies: accepts: ~1.3.8 array-flatten: 1.1.1 @@ -10577,7 +10577,7 @@ __metadata: methods: ~1.1.2 on-finished: 2.4.1 parseurl: ~1.3.3 - path-to-regexp: 0.1.10 + path-to-regexp: 0.1.12 proxy-addr: ~2.0.7 qs: 6.13.0 range-parser: ~1.2.1 @@ -10589,7 +10589,7 @@ __metadata: type-is: ~1.6.18 utils-merge: 1.0.1 vary: ~1.1.2 - checksum: 5ac2b26d8aeddda5564fc0907227d29c100f90c0ead2ead9d474dc5108e8fb306c2de2083c4e3ba326e0906466f2b73417dbac16961f4075ff9f03785fd940fe + checksum: 3aef1d355622732e20b8f3a7c112d4391d44e2131f4f449e1f273a309752a41abfad714e881f177645517cbe29b3ccdc10b35e7e25c13506114244a5b72f549d languageName: node linkType: hard @@ -16032,11 +16032,11 @@ __metadata: linkType: hard "nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2 + checksum: dfe0adbc0c77e9655b550c333075f51bb28cfc7568afbf3237249904f9c86c9aaaed1f113f0fddddba75673ee31c758c30c43d4414f014a52a7a626efc5958c9 languageName: node linkType: hard @@ -17047,10 +17047,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: ab7a3b7a0b914476d44030340b0a65d69851af2a0f33427df1476100ccb87d409c39e2182837a96b98fb38c4ef2ba6b87bdad62bb70a2c153876b8061760583c +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: ab237858bee7b25ecd885189f175ab5b5161e7b712b360d44f5c4516b8d271da3e4bf7bf0a7b9153ecb04c7d90ce8ff5158614e1208819cf62bac2b08452722e languageName: node linkType: hard