diff --git a/.eslintrc.json b/.eslintrc.json index dfd4106..841989b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "plugins": [], - "ignorePatterns": ["dist"], + "ignorePatterns": ["importsJSONfile.js"], "extends": [ "eslint:recommended", "plugin:markdown/recommended" diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a7eb1..0b7dc82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * 2.5.9 _Nov.11.2023_ * [mock Array-type default export,](https://github.com/iambumblehead/esmock/pull/266) thanks @altearius + * [support json import mocking,](https://github.com/iambumblehead/esmock/pull/247) only working at node v21, see [node#49724](https://github.com/nodejs/node/issues/49724) * 2.5.8 _Oct.23.2023_ * [catch yarn PnP exceptions](https://github.com/iambumblehead/esmock/pull/262) @koshic * 2.5.7 _Oct.20.2023_ diff --git a/src/esmockCache.js b/src/esmockCache.js index 1bb2fd7..5c1d6f9 100644 --- a/src/esmockCache.js +++ b/src/esmockCache.js @@ -8,6 +8,13 @@ const esmockCache = { mockDefs: {} } +const esmockModuleIdSourceSet = (keysource, source) => ( + esmockPostMessage({ keysource, source }), + global.mockKeysSource[String(keysource)] = source) + +const esmockModuleIdSourceGet = keysource => ( + global.mockKeysSource[String(keysource)]) + const esmockTreeIdSet = (key, keylong) => ( esmockPostMessage({ key, keylong }), global.mockKeys[String(key)] = keylong) @@ -31,7 +38,8 @@ Object.assign(global, { esmockCache, esmockCacheGet, esmockTreeIdGet, - mockKeys: global.mockKeys || {} + mockKeys: global.mockKeys || {}, + mockKeysSource: global.mockKeysSource || {} }) export { @@ -40,6 +48,8 @@ export { esmockCacheGet, esmockTreeIdSet, esmockTreeIdGet, + esmockModuleIdSourceSet, + esmockModuleIdSourceGet, esmockCacheResolvedPathIsESMGet, esmockCacheResolvedPathIsESMSet } diff --git a/src/esmockLoader.js b/src/esmockLoader.js index 7c707c0..2c04923 100644 --- a/src/esmockLoader.js +++ b/src/esmockLoader.js @@ -31,11 +31,14 @@ const moduleIdReCreate = (moduleid, treeid) => new RegExp( // node v12.0-v18.x, global const mockKeys = global.mockKeys = (global.mockKeys || {}) +const mockKeysSource = global.mockKeysSource = (global.mockKeysSource || {}) // node v20.0-v20.6 const globalPreload = !module.register && (({ port }) => ( port.addEventListener('message', ev => ( - mockKeys[ev.data.key] = ev.data.keylong)), + ev.data.keysource + ? mockKeysSource[ev.data.keysource] = ev.data.source + : mockKeys[ev.data.key] = ev.data.keylong)), port.unref(), 'global.postMessageEsmk = d => port.postMessage(d)' )) @@ -44,7 +47,9 @@ const globalPreload = !module.register && (({ port }) => ( const initialize = module.register && (data => { if (data && data.port) { data.port.on('message', msg => { - mockKeys[msg.key] = msg.keylong + msg.keysource + ? mockKeysSource[msg.keysource] = msg.source + : mockKeys[msg.key] = msg.keylong }) } }) @@ -198,6 +203,16 @@ const load = async (url, context, nextLoad) => { .split(',') if (exportedNames && exportedNames[0]) { + if (mockKeysSource[url]) { + return { + // ...await nextLoad(url, context), + format: 'json', + shortCircuit: true, + responseURL: encodeURI(url), + source: mockKeysSource[url] + } + } + return { format: 'module', shortCircuit: true, diff --git a/src/esmockModule.js b/src/esmockModule.js index db4ba24..214b0af 100644 --- a/src/esmockModule.js +++ b/src/esmockModule.js @@ -1,4 +1,5 @@ import fs from 'fs' +import url from 'url' import resolvewith from 'resolvewithplus' import esmockErr from './esmockErr.js' import esmockIsESMRe from './esmockIsESMRe.js' @@ -7,6 +8,7 @@ import { esmockTreeIdSet, esmockTreeIdGet, esmockCacheSet, + esmockModuleIdSourceSet, esmockCacheResolvedPathIsESMGet, esmockCacheResolvedPathIsESMSet } from './esmockCache.js' @@ -18,6 +20,7 @@ const nextId = ((id = 0) => () => ++id)() const objProto = Object.getPrototypeOf({}) const isPlainObj = o => Object.getPrototypeOf(o) === objProto const iscoremodule = resolvewith.iscoremodule +const isJSONExtnRe = /\.json$/i // assigning the object to its own prototypal inheritor can error, eg // 'Cannot assign to read only property \'F_OK\' of object \'#\'' @@ -36,7 +39,7 @@ const esmockModuleMergeDefault = (defLive, def) => const esmockModuleApply = (defLive, def, fileURL) => { // no fileURL here indicates 'import' mock, 'default' not needed - if (fileURL === null) + if (fileURL === null || isJSONExtnRe.test(fileURL)) return Object.assign({}, defLive || {}, def) if (Array.isArray(def)) @@ -95,9 +98,13 @@ const esmockModuleImportedPurge = treeid => { String(url.split('esmkgdefs=')[1]).split('#-#').forEach(purgeKey) } +const esmockImport = async fileURL => isJSONExtnRe.test(fileURL) + ? JSON.parse(fs.readFileSync(new url.URL(fileURL), 'utf-8')) + : import(fileURL) + const esmockModuleCreate = async (treeid, def, id, fileURL, opt) => { def = esmockModuleApply( - opt.strict || !fileURL || await import(fileURL), def, fileURL) + opt.strict || !fileURL || await esmockImport(fileURL), def, fileURL) const mockModuleKey = (fileURL || 'file:///' + id) + '?' + [ 'esmkTreeId=' + treeid, @@ -107,6 +114,10 @@ const esmockModuleCreate = async (treeid, def, id, fileURL, opt) => { 'exportNames=' + Object.keys(def).sort().join() ].join('&') + if (isJSONExtnRe.test(fileURL)) { + esmockModuleIdSourceSet(mockModuleKey, JSON.stringify(def)) + } + esmockCacheSet(mockModuleKey, def) return mockModuleKey diff --git a/tests/local/example.json b/tests/local/example.json new file mode 100644 index 0000000..4fa8f88 --- /dev/null +++ b/tests/local/example.json @@ -0,0 +1 @@ +{ "example": "json" } diff --git a/tests/local/importsJSONfile.js b/tests/local/importsJSONfile.js new file mode 100644 index 0000000..f1a3c8b --- /dev/null +++ b/tests/local/importsJSONfile.js @@ -0,0 +1,5 @@ +import JSONobj from './example.json' assert { type: 'json' }; + +export { + JSONobj +} diff --git a/tests/tests-node/esmock.node.test.js b/tests/tests-node/esmock.node.test.js index c2f3582..8b02f1f 100644 --- a/tests/tests-node/esmock.node.test.js +++ b/tests/tests-node/esmock.node.test.js @@ -548,3 +548,36 @@ test('should mock an exported array', async () => { assert.deepStrictEqual(importsArray(), ['mocked']) }) + +test('should mock imported json', async () => { + const importsJSON = await esmock( + '../local/importsJSONfile.js', { + '../local/example.json': { + 'test-example': 'test-json-a' + } + }) + + if (/^(18|20)$/.test(process.versions.node.split('.')[0])) + return assert.ok(true) + + assert.strictEqual( + Object.keys(importsJSON.JSONobj).sort().join(), 'example,test-example') + assert.strictEqual(importsJSON.JSONobj['test-example'], 'test-json-a') + assert.strictEqual(importsJSON.JSONobj['example'], 'json') +}) + +test('should mock imported json (strict)', async () => { + const importsJSON = await esmock.strict( + '../local/importsJSONfile.js', { + '../local/example.json': { + 'test-example': 'test-json-b' + } + }) + + if (/^(18|20)$/.test(process.versions.node.split('.')[0])) + return assert.ok(true) + + assert.strictEqual( + Object.keys(importsJSON.JSONobj).sort().join(), 'test-example') + assert.strictEqual(importsJSON.JSONobj['test-example'], 'test-json-b') +})