From d021e8b7358177ede81360b1399b76282d773d2e Mon Sep 17 00:00:00 2001 From: Ross Brandes Date: Fri, 9 Oct 2026 20:45:42 -0400 Subject: [PATCH] Add regression tests for mocking by package.json imports specifiers To prevent a regression of #69. quibble.esm('#src/...') used to fail with ERR_PACKAGE_IMPORT_NOT_DEFINED because the dummy import() that finds a specifier's URL ran from quibble's own file, so Node resolved the specifier against quibble's package.json. Resolving non-relative specifiers from the caller (#123, 0.12.0) fixed this too, since `#` specifiers count as bare, but nothing tested it. The fixture is its own package with an `imports` field, and the calls to quibble come from a file inside it. The tests and quibble's lib directory share quibble's root package.json, so mapping the alias there would let 0.11.0 resolve it too and the tests would never fail. They do fail against 0.11.0's lib. --- .../package-with-imports/package.json | 8 ++++++ .../package-with-imports/src/a-module.mjs | 2 ++ .../package-with-imports/uses-imports.mjs | 14 ++++++++++ test/esm-lib/quibble-esm.test.mjs | 27 +++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 test/esm-fixtures/package-with-imports/package.json create mode 100644 test/esm-fixtures/package-with-imports/src/a-module.mjs create mode 100644 test/esm-fixtures/package-with-imports/uses-imports.mjs diff --git a/test/esm-fixtures/package-with-imports/package.json b/test/esm-fixtures/package-with-imports/package.json new file mode 100644 index 0000000..60bd218 --- /dev/null +++ b/test/esm-fixtures/package-with-imports/package.json @@ -0,0 +1,8 @@ +{ + "name": "package-with-imports", + "description": "Fixture whose package.json maps #src/* imports, which only files in this package can resolve (#69)", + "private": true, + "imports": { + "#src/*": "./src/*" + } +} diff --git a/test/esm-fixtures/package-with-imports/src/a-module.mjs b/test/esm-fixtures/package-with-imports/src/a-module.mjs new file mode 100644 index 0000000..710291d --- /dev/null +++ b/test/esm-fixtures/package-with-imports/src/a-module.mjs @@ -0,0 +1,2 @@ +export default 'default-export' +export const namedExport = 'named-export' diff --git a/test/esm-fixtures/package-with-imports/uses-imports.mjs b/test/esm-fixtures/package-with-imports/uses-imports.mjs new file mode 100644 index 0000000..3bece06 --- /dev/null +++ b/test/esm-fixtures/package-with-imports/uses-imports.mjs @@ -0,0 +1,14 @@ +// Lives in a package whose package.json maps `#src/*`, so `#src/...` specifiers +// only resolve from files in this package, not from the tests or from quibble +import quibble from '../../../lib/quibble.mjs' +import wrappedQuibbleEsm from '../a-quibble-esm-wrapper.mjs' + +export const mock = (namedExportStubs, defaultExportStub) => + quibble.esm('#src/a-module.mjs', namedExportStubs, defaultExportStub) + +export const mockThroughWrapper = (namedExportStubs, defaultExportStub) => + wrappedQuibbleEsm('#src/a-module.mjs', namedExportStubs, defaultExportStub) + +export const importWithPath = () => quibble.esmImportWithPath('#src/a-module.mjs') + +export const importModule = () => import('#src/a-module.mjs') diff --git a/test/esm-lib/quibble-esm.test.mjs b/test/esm-lib/quibble-esm.test.mjs index ea61323..79ede92 100644 --- a/test/esm-lib/quibble-esm.test.mjs +++ b/test/esm-lib/quibble-esm.test.mjs @@ -158,6 +158,33 @@ export default { assert.equal(result.default, 'default-export-replacement') assert.equal(result.namedExport, 'replacement') }, + 'mock a module by a package.json imports specifier (#src/...) only the caller can resolve': async function () { + const usesImports = await import('../esm-fixtures/package-with-imports/uses-imports.mjs') + await usesImports.mock({ namedExport: 'replacement' }, 'default-export-replacement') + + const result = await usesImports.importModule() + assert.equal(result.default, 'default-export-replacement') + assert.equal(result.namedExport, 'replacement') + + const viaPath = await import('../esm-fixtures/package-with-imports/src/a-module.mjs') + assert.equal(viaPath.default, 'default-export-replacement') + }, + 'mock a module by a package.json imports specifier through a wrapper that ignores itself': async function () { + const usesImports = await import('../esm-fixtures/package-with-imports/uses-imports.mjs') + await usesImports.mockThroughWrapper({ namedExport: 'replacement' }, 'default-export-replacement') + + const result = await usesImports.importModule() + assert.equal(result.default, 'default-export-replacement') + assert.equal(result.namedExport, 'replacement') + }, + 'esmImportWithPath resolves a package.json imports specifier from the caller': async function () { + const usesImports = await import('../esm-fixtures/package-with-imports/uses-imports.mjs') + const { moduleUrl, module } = await usesImports.importWithPath() + + assert.equal(moduleUrl, new URL('../esm-fixtures/package-with-imports/src/a-module.mjs', import.meta.url).href) + assert.equal(module.default, 'default-export') + assert.equal(module.namedExport, 'named-export') + }, 'isLoaderLoader returns true if loader as loaded': async function () { assert.equal(quibble.isLoaderLoaded(), true) },