diff --git a/CHANGELOG.md b/CHANGELOG.md index d1575ca..340d24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# Unreleased + +* Fix CommonJS modules loaded with `import()` having empty exports after + `quibble()` was called from the importing file (Node 20.6–24.17 and 25.x) + [#111](https://github.com/testdouble/quibble/issues/111). As a side effect, + if a file that called `quibble()` is part of a circular `require()`, it now + gets the partially loaded module, as it would without quibble, instead of + a second copy + # 0.12.1 * Fix mocking an ES module with a named export whose name isn't an identifier diff --git a/lib/quibble.js b/lib/quibble.js index fb88971..f0fc526 100644 --- a/lib/quibble.js +++ b/lib/quibble.js @@ -192,6 +192,11 @@ const requireWasCalledFromAFileThatHasQuibbledStuff = function () { const doWithoutCache = function (request, parent, thingToDo) { const filename = Module._resolveFilename(request, parent) if (Object.prototype.hasOwnProperty.call(Module._cache, filename)) { + if (Module._cache[filename]?.loaded === false) { + // Node's ESM loader caches a CommonJS module before loading it, then + // reads the exports off that cache entry, so load it where it is + return thingToDo() + } return doAndRestoreCache(filename, thingToDo) } else { return doAndDeleteCache(filename, thingToDo) diff --git a/test/esm-fixtures/a-cjs-dependency.cjs b/test/esm-fixtures/a-cjs-dependency.cjs new file mode 100644 index 0000000..f2f754f --- /dev/null +++ b/test/esm-fixtures/a-cjs-dependency.cjs @@ -0,0 +1 @@ +module.exports = 'the real dependency' diff --git a/test/esm-fixtures/a-cjs-module-with-a-dependency.cjs b/test/esm-fixtures/a-cjs-module-with-a-dependency.cjs new file mode 100644 index 0000000..8fe434d --- /dev/null +++ b/test/esm-fixtures/a-cjs-module-with-a-dependency.cjs @@ -0,0 +1,3 @@ +// Only ever imported by the test that quibbles its dependency, so that +// import() is what first loads it +module.exports = { dependency: require('./a-cjs-dependency.cjs') } diff --git a/test/esm-lib/quibble-esm.test.mjs b/test/esm-lib/quibble-esm.test.mjs index 79ede92..6aa8987 100644 --- a/test/esm-lib/quibble-esm.test.mjs +++ b/test/esm-lib/quibble-esm.test.mjs @@ -56,6 +56,14 @@ export default { assert.equal(result[name], 'replacement') } }, + 'import() a CommonJS module that requires a quibbled module': async function () { + // Node's ESM loader caches a not-yet-loaded CommonJS module before + // requiring it, then reads the exports off that same cache entry + quibble('../esm-fixtures/a-cjs-dependency.cjs', 'a fake dependency') + + const { default: result } = await import('../esm-fixtures/a-cjs-module-with-a-dependency.cjs') + assert.equal(result.dependency, 'a fake dependency') + }, 'mock a module after it is used unmocked': async function () { const result1 = await import('../esm-fixtures/a-module.mjs') assert.deepEqual({ ...result1 }, {