diff --git a/packages/babel-register/src/nodeWrapper.ts b/packages/babel-register/src/nodeWrapper.ts index 1839dfc7e44a..27b3cead2f80 100644 --- a/packages/babel-register/src/nodeWrapper.ts +++ b/packages/babel-register/src/nodeWrapper.ts @@ -13,6 +13,9 @@ const internalModuleCache = Object.create(null); Module._cache = internalModuleCache; const node = require("./node"); + +// NOTE: This Module._cache set is intercepted by the beforeEach hook in +// packages/babel-register/test/index.js to install dependencies mocks. Module._cache = globalModuleCache; // Add source-map-support to global cache as it's stateful diff --git a/packages/babel-register/test/cache.js b/packages/babel-register/test/cache.js index f9db0d0e46f0..8c9de253cd34 100644 --- a/packages/babel-register/test/cache.js +++ b/packages/babel-register/test/cache.js @@ -1,13 +1,13 @@ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; -import { createRequire } from "module"; +import { createRequire, Module } from "module"; const require = createRequire(import.meta.url); const testCacheFilename = path.join( path.dirname(fileURLToPath(import.meta.url)), - ".babel", + ".cache.babel", ); const oldBabelDisableCacheValue = process.env.BABEL_DISABLE_CACHE; @@ -35,14 +35,30 @@ function resetCache() { process.env.BABEL_DISABLE_CACHE = oldBabelDisableCacheValue; } +const OLD_JEST_MOCKS = !!jest.doMock; + describe("@babel/register - caching", () => { describe("cache", () => { let load, get, setDirty, save; let consoleWarnSpy; + if (!OLD_JEST_MOCKS) { + let oldModuleCache; + beforeAll(() => { + oldModuleCache = Module._cache; + }); + afterAll(() => { + Module._cache = oldModuleCache; + }); + } + beforeEach(() => { // Since lib/cache is a singleton we need to fully reload it - jest.resetModules(require); + if (OLD_JEST_MOCKS) { + jest.resetModules(); + } else { + Module._cache = {}; + } const cache = require("../lib/cache.js"); load = cache.load; diff --git a/packages/babel-register/test/fixtures/package.json b/packages/babel-register/test/fixtures/package.json new file mode 100644 index 000000000000..a3c15a7a6312 --- /dev/null +++ b/packages/babel-register/test/fixtures/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/packages/babel-register/test/index.js b/packages/babel-register/test/index.js index 8fb0ee52c7b5..c813ddce47f9 100644 --- a/packages/babel-register/test/index.js +++ b/packages/babel-register/test/index.js @@ -1,47 +1,19 @@ -import { createRequire } from "module"; +import { createRequire, Module } from "module"; import path from "path"; import fs from "fs"; import child from "child_process"; +import { fileURLToPath } from "url"; +const dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); -let currentHook; -let currentOptions; -let sourceMapSupport = false; - const registerFile = require.resolve("../lib/index"); -const testCacheFilename = path.join(__dirname, ".babel"); +const testCacheFilename = path.join(dirname, ".index.babel"); const testFile = require.resolve("./fixtures/babelrc/es2015"); const testFileContent = fs.readFileSync(testFile); -const sourceMapTestFile = require.resolve("./fixtures/source-map/index"); -const sourceMapNestedTestFile = require.resolve( - "./fixtures/source-map/foo/bar", -); -const internalModulesTestFile = require.resolve( - "./fixtures/internal-modules/index", -); - -jest.mock("pirates", () => { - return { - addHook(hook, opts) { - currentHook = hook; - currentOptions = opts; - - return () => { - currentHook = null; - currentOptions = null; - }; - }, - }; -}); -jest.mock("source-map-support", () => { - return { - install() { - sourceMapSupport = true; - }, - }; -}); +const piratesPath = require.resolve("pirates"); +const smsPath = require.resolve("source-map-support"); const defaultOptions = { exts: [".js", ".jsx", ".es6", ".es", ".mjs", ".cjs"], @@ -60,7 +32,31 @@ function resetCache() { process.env.BABEL_CACHE_PATH = null; } +const OLD_JEST_MOCKS = !!jest.doMock; + describe("@babel/register", function () { + let currentHook, currentOptions, sourceMapSupport; + + const mocks = { + ["pirates"]: { + addHook(hook, opts) { + currentHook = hook; + currentOptions = opts; + + return () => { + currentHook = null; + currentOptions = null; + }; + }, + }, + + ["source-map-support"]: { + install() { + sourceMapSupport = true; + }, + }, + }; + let babelRegister; function setupRegister(config = { babelrc: false }) { @@ -83,6 +79,12 @@ describe("@babel/register", function () { cleanCache(); } + beforeEach(() => { + currentHook = null; + currentOptions = null; + sourceMapSupport = false; + }); + afterEach(async () => { // @babel/register saves the cache on process.nextTick. // We need to wait for at least one tick so that when jest @@ -91,16 +93,60 @@ describe("@babel/register", function () { await new Promise(setImmediate); revertRegister(); - currentHook = null; - currentOptions = null; - sourceMapSupport = false; - jest.resetModules(); }); afterAll(() => { resetCache(); }); + if (OLD_JEST_MOCKS) { + jest.doMock("pirates", () => mocks["pirates"]); + jest.doMock("source-map-support", () => mocks["source-map-support"]); + + afterEach(() => { + jest.resetModules(); + }); + } else { + let originalRequireCacheDescriptor; + beforeAll(() => { + originalRequireCacheDescriptor = Object.getOwnPropertyDescriptor( + Module, + "_cache", + ); + }); + + beforeEach(() => { + const isEmptyObj = obj => + Object.getPrototypeOf(obj) === null && Object.keys(obj).length === 0; + + // This setter intercepts the Module._cache assignment in + // packages/babel-register/src/nodeWrapper.js to install in the + // internal isolated cache. + const emptyInitialCache = {}; + Object.defineProperty(Module, "_cache", { + get: () => emptyInitialCache, + set(value) { + expect(isEmptyObj(value)).toBe(true); + + Object.defineProperty(Module, "_cache", { + value, + enumerable: originalRequireCacheDescriptor.enumerable, + configurable: originalRequireCacheDescriptor.configurable, + writable: originalRequireCacheDescriptor.writable, + }); + value[piratesPath] = { exports: mocks["pirates"] }; + value[smsPath] = { exports: mocks["source-map-support"] }; + }, + enumerable: originalRequireCacheDescriptor.enumerable, + configurable: originalRequireCacheDescriptor.configurable, + }); + }); + + afterAll(() => { + Object.defineProperty(Module, "_cache", originalRequireCacheDescriptor); + }); + } + test("registers hook correctly", () => { setupRegister(); @@ -160,11 +206,11 @@ describe("@babel/register", function () { const output = await spawnNodeAsync([ "-r", registerFile, - sourceMapTestFile, + require.resolve("./fixtures/source-map/index"), ]); const sourceMap = JSON.parse(output); expect(sourceMap.map.sourceRoot + sourceMap.map.sources[0]).toBe( - sourceMapNestedTestFile, + require.resolve("./fixtures/source-map/foo/bar"), ); }); @@ -195,14 +241,16 @@ describe("@babel/register", function () { // Need a clean environment without `convert-source-map` // already in the require cache, so we spawn a separate process - const output = await spawnNodeAsync([internalModulesTestFile]); + const output = await spawnNodeAsync([ + require.resolve("./fixtures/internal-modules/index.js"), + ]); const { convertSourceMap } = JSON.parse(output); expect(convertSourceMap).toMatch("/* transformed */"); }); }); function spawnNodeAsync(args) { - const spawn = child.spawn(process.execPath, args, { cwd: __dirname }); + const spawn = child.spawn(process.execPath, args, { cwd: dirname }); let output = ""; let callback; diff --git a/test/jest-light-runner/src/global-setup.js b/test/jest-light-runner/src/global-setup.js index 5d012866d45b..6db8e0197451 100644 --- a/test/jest-light-runner/src/global-setup.js +++ b/test/jest-light-runner/src/global-setup.js @@ -24,7 +24,4 @@ globalThis.afterEach = circus.afterEach; globalThis.jest = { fn: mock.fn, spyOn: mock.spyOn, - resetModules: require => { - for (const k of Object.keys(require.cache)) delete require.cache[k]; - }, };