Skip to content

Commit

Permalink
Merge pull request #23 from iambumblehead/add-low-tech-support-for-in…
Browse files Browse the repository at this point in the history
…line-await-import-mock

add test and support for await import
  • Loading branch information
iambumblehead authored Nov 26, 2021
2 parents 88bb715 + 60406a3 commit b1cfd87
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 15 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,49 @@ test('should mock modules and local files at same time', async t => {
});
```

## Examle, mocking await import( 'modulename' )

When `esmock` loads and returns a module it deletes mocking definitions from a cache by default. Disable this behaviour when using 'await import', so that mocked definitions can be loaded during test runtime. A function can be called later to clear the cache.

``` javascript
export default async function usesAwaitImport (config) {
const eslint = (await import('eslint'));

return new eslint.ESLint({ baseConfig : config });
};
```

In the test file, define an 'options' object before local and global mock definitions, `{ isPurge: false }`,
``` javascript
test('mocks inline `async import("name")`', async t => {
const writeJSConfigFile = await esmock('./local/usesAwaitImport.mjs', {
isPurge : false
}, {
eslint : {
ESLint : function (o) {
this.stringify = () => JSON.stringify(o);

return this;
}
}
});

t.is((await writeJSConfigFile('config')).stringify(), JSON.stringify({
baseConfig : 'config'
}));

// clear the cache
esmock.purge(writeJSConfigFile);
});
```

If there are not many tests or if each test completes in a separate process, skipping `esmock.purge()` is OK


### changelog

* 1.2.0 _Nov.26.2021_
* add support for await import
* 1.1.0 _Nov.25.2021_
* add windows-latest to testing pipeline and begin windows support
* removed files and functions no longer needed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "esmock",
"version": "1.1.0",
"version": "1.2.0",
"license": "MIT",
"readmeFilename": "README.md",
"description": "mock esm modules for unit-tests",
Expand Down
33 changes: 33 additions & 0 deletions spec/esmock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,36 @@ test('returns spread-imported [object Module] default export', async t => {

t.is(main.exportedFunction(), 'foobar');
});

test('mocks inline `async import("name")`', async t => {
const writeJSConfigFile = await esmock('./local/usesInlineImport.mjs', {
isPurge : false
}, {
eslint : {
ESLint : function (...o) {
this.stringify = () => JSON.stringify(...o);

return this;
}
}
});

t.is(
(await writeJSConfigFile('config', 'filePath')).stringify(),
JSON.stringify({
baseConfig : 'config',
fix : true,
useEslintrc : false,
filePath : 'filePath'
}));

const [ , key ] = writeJSConfigFile.esmockKey.match(/esmockKey=(\d*)/);
const keyRe = new RegExp(`esmockKey=${key}[^d]`);

const moduleKeys = Object.keys(esmock.esmockCache.mockDefs)
.filter(moduleKey => keyRe.test(moduleKey));

t.true(moduleKeys.every(mkey => esmock.esmockCache.mockDefs[mkey]));
esmock.purge(writeJSConfigFile);
t.true(moduleKeys.every(mkey => esmock.esmockCache.mockDefs[mkey] === null));
});
13 changes: 13 additions & 0 deletions spec/local/usesInlineImport.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

async function writeJSConfigFile (config, filePath) {
const eslint = (await import('eslint'));

return new eslint.ESLint({
baseConfig : config,
fix : true,
useEslintrc : false,
filePath
});
};

export default writeJSConfigFile;
19 changes: 17 additions & 2 deletions src/esmock.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,35 @@ import {
esmockCache
} from './esmockCache.js';

const argHasKey = (arg, key) => (
arg && !/number|boolean/.test(typeof arg) && key in arg);

const esmock = async (modulePath, mockDefs, globalDefs, opt) => {
// this functions caller is stack item '2'
const calleePath = new Error().stack.split('\n')[2]
.replace(/^.*file:\/\//, '') // rm everything before filepathfe
.replace(/:[\d]*:[\d]*.*$/, '') // rm line and row number
.replace(/^.*:/, ''); // rm windows-style drive locations

// remap params in case options are provided as first arg
[ opt, mockDefs, globalDefs ] = argHasKey(mockDefs, 'isPurge')
? [ mockDefs || {}, globalDefs || {}, opt || {} ]
: [ opt || {}, mockDefs || {}, globalDefs || {} ];

const modulePathKey = await esmockModuleMock(
calleePath, modulePath, mockDefs || {}, globalDefs || {}, opt || {});

const importedModule = await import(modulePathKey);

esmockModuleImportedPurge(modulePathKey);
if (opt.isPurge !== false)
esmockModuleImportedPurge(modulePathKey);

return esmockModuleImportedSanitize(importedModule);
return esmockModuleImportedSanitize(importedModule, modulePathKey);
};

esmock.purge = mockModule => {
if (argHasKey(mockModule, 'esmockKey'))
esmockModuleImportedPurge(mockModule.esmockKey);
};

esmock.esmockCache = esmockCache;
Expand Down
6 changes: 0 additions & 6 deletions src/esmockCache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import path from 'path';

const esmockCache = {
resolvedPaths : {},
isESM : {},
Expand All @@ -15,9 +13,6 @@ const esmockCacheSet = (key, mockDef) => (
const esmockCacheGet = key => (
esmockCache.mockDefs[key]);

const esmockCacheResolvePathKey = (calleePath, localPath) => (
path.join(path.dirname(calleePath), localPath));

const esmockCacheResolvedPathIsESMGet = mockPathFull => (
esmockCache.isESM[mockPathFull]);

Expand All @@ -30,7 +25,6 @@ export {
esmockCache,
esmockCacheSet,
esmockCacheGet,
esmockCacheResolvePathKey,
esmockCacheResolvedPathIsESMGet,
esmockCacheResolvedPathIsESMSet
};
8 changes: 4 additions & 4 deletions src/esmockModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,22 @@ const esmockModuleIsESM = (mockPathFull, isesm) => {

// return the default value directly, so that the esmock caller
// does not need to lookup default as in "esmockedValue.default"
const esmockModuleImportedSanitize = importedModule => {
const esmockModuleImportedSanitize = (importedModule, esmockKey) => {
const importedDefault = 'default' in importedModule
&& importedModule.default;

if (!/boolean|string|number/.test(typeof importedDefault)) {
// an example of [object Module]: import * as mod from 'fs'; export mod;
return Object.prototype.toString.call(importedDefault) === '[object Module]'
? Object.assign({}, importedDefault, importedModule)
: Object.assign(importedDefault, importedModule);
? Object.assign({}, importedDefault, importedModule, { esmockKey })
: Object.assign(importedDefault, importedModule, { esmockKey });
}

return importedModule;
};

const esmockModuleImportedPurge = modulePathKey => {
const purgeKey = key => esmockCacheSet(key, null);
const purgeKey = key => key !== 'null' && esmockCacheSet(key, null);
const [ url, keys ] = modulePathKey.split('#esmockModuleKeys=');

String(keys).split('#').forEach(purgeKey);
Expand Down

0 comments on commit b1cfd87

Please sign in to comment.