Skip to content

Commit

Permalink
Merge pull request #141 from iambumblehead/use-partial-variant-as-def…
Browse files Browse the repository at this point in the history
…ault-export

use partial variant as default export
  • Loading branch information
iambumblehead authored Sep 6, 2022
2 parents 336540f + ea6b295 commit 302ca82
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 105 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# changelog

* 2.0.0 _Sep.06.2022_
* [export both 'partial' and 'strict'](https://github.com/iambumblehead/esmock/pull/140) variants of esmock
* [export a 'strict'](https://github.com/iambumblehead/esmock/pull/140) variant of esmock
* [use 'partial' mock behaviour with default export](https://github.com/iambumblehead/esmock/pull/141)
* updated readme,
* resolve error when partial mocking modules not found on filesystem
* rename option `isPackageNotFoundError` to `isModuleNotFoundError`
* [see the release announcement](https://github.com/iambumblehead/esmock/releases/tag/v2.0.0) for details and migration guide
* 1.9.8 _Aug.28.2022_
* [use latest node v18](https://github.com/iambumblehead/esmock/pull/130) for ci-tests, a bug in the ava package prevented this
* [use latest resolvewithplus](https://github.com/iambumblehead/esmock/pull/130) and remove many lines of code needed for the older variant
Expand Down
20 changes: 5 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
[2]: https://github.com/iambumblehead/esmock "esmock"
[3]: https://github.com/iambumblehead/esmock/tree/master/tests "tests"


`esmock` is used with node's --loader
``` json
{
Expand Down Expand Up @@ -90,25 +89,16 @@ test('should mock "await import()" using esmock.p', async () => {
// a bit more info are found in the wiki guide
})

// a "partial mock" merges the new and original definitions
test('should suppport partial mocks', async () => {
const pathWrap = await esmock('../src/pathWrap.js', {
test('should support "strict" mocking, at esmock.strict', async () => {
// strict mock definitions are not merged w/ original module definitions
const pathWrapper = await esmock.strict('../src/pathWrapper.js', {
path: { dirname: () => '/path/to/file' }
})

// an error, because path.basename was not defined
await assert.rejects(async () => pathWrap.basename('/dog.png'), {
// error, because the "path" mock above does not define path.basename
await assert.rejects(async () => pathWrapper.basename('/dog.png'), {
name: 'TypeError',
message: 'path.basename is not a function'
})

// use esmock.partial to create a "partial mock"
const pathWrapPartial = await esmock.partial('../src/pathWrap.js', {
path: { dirname: () => '/home/' }
})

// no error, because "core" path.basename was merged into the mock
assert.deepEqual(pathWrapPartial.basename('/dog.png'), 'dog.png')
assert.deepEqual(pathWrapPartial.dirname(), '/home/')
})
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "esmock",
"type": "module",
"version": "1.9.8",
"version": "2.0.0",
"license": "ISC",
"readmeFilename": "README.md",
"description": "provides native ESM import mocking for unit tests",
Expand Down
34 changes: 5 additions & 29 deletions src/esmock.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* Mocks imports for the module specified by {@link modulePath}.
*
* The provided mocks replace the imported modules _fully_.
* By default, mock definitions are merged with the original module definitions.
* To disable the default behaviour, Use esmock.strict.
*
* @param modulePath The module whose imports will be mocked.
* @param parent A URL to resolve specifiers relative to; typically `import.meta.url`.
Expand All @@ -20,37 +21,12 @@ declare function esmock(modulePath: string, mockDefs?: Record<string, any>, glob

declare namespace esmock {
interface Options {
partial?: boolean | undefined;
strict?: boolean | undefined;
purge?: boolean | undefined;
isPackageNotFoundError?: boolean | undefined;
isModuleNotFoundError?: boolean | undefined;
parent?: string | undefined;
}

/**
* Mocks imports for the module specified by {@link modulePath}.
*
* This "partial" variant gives mock definitions that are merged with the
* original module definitions.
*
* @param modulePath The module whose imports will be mocked.
* @param parent A URL to resolve specifiers relative to; typically `import.meta.url`.
* If not specified, it will be inferred via the stack, which may not work
* if source maps are in use.
* @param mockDefs A mapping of import specifiers to mocked module objects; these mocks will
* only be used for imports resolved in the module specified by {@link modulePath}.
* @param globalDefs A mapping of import specifiers to mocked module objects; these mocks will
* apply to imports within the module specified by {@link modulePath}, as well
* as any transitively imported modules.
* @param opt
* @returns The result of importing {@link modulePath}, similar to `import(modulePath)`.
*/
function partial(modulePath: string, parent: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
function partial(modulePath: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
export namespace partial {
function p(modulePath: string, parent: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
function p(modulePath: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
}

/**
* Mocks imports for the module specified by {@link modulePath}.
*
Expand Down Expand Up @@ -104,4 +80,4 @@ declare namespace esmock {
}

export default esmock;
export { esmock as partial, esmock as strict };
export { esmock as strict };
15 changes: 6 additions & 9 deletions src/esmock.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,15 @@ const esmock = async (...args) => {

return esmockModuleImportedSanitize(importedModule, modulePathKey)
}
esmock.p = async (...args) => esmock(
...esmockArgs(args, { purge: false }, new Error))

const strict = async (...args) => esmock(
...esmockArgs(args, { partial: false }, new Error))
...esmockArgs(args, { strict: true }, new Error))
strict.p = async (...args) => esmock(
...esmockArgs(args, { partial: false, purge: false }, new Error))
...esmockArgs(args, { strict: true, purge: false }, new Error))

const partial = async (...args) => esmock(
...esmockArgs(args, { partial: true }, new Error))
partial.p = async (...args) => esmock(
...esmockArgs(args, { partial: true, purge: false }, new Error))

Object.assign(esmock, strict, { strict, partial })
Object.assign(esmock, { strict })

esmock.purge = mockModule => {
if (mockModule && /object|function/.test(typeof mockModule)
Expand All @@ -53,4 +50,4 @@ esmock.purge = mockModule => {

esmock.esmockCache = esmockCache

export {esmock as default, partial, strict}
export {esmock as default, strict}
5 changes: 3 additions & 2 deletions src/esmockModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ const esmockNextKey = ((key = 0) => () => ++key)()
// eslint-disable-next-line max-len
const esmockModuleCreate = async (esmockKey, key, mockPathFull, mockDef, opt) => {
const isesm = esmockModuleIsESM(mockPathFull)
const originalDefinition = opt.partial ? await import(mockPathFull) : null
const originalDefinition = opt.strict || opt.isfound === false
|| await import(mockPathFull)
const mockDefinitionFinal = esmockModuleApply(
originalDefinition, mockDef, mockPathFull)
const mockExportNames = Object.keys(mockDefinitionFinal).sort().join()
Expand All @@ -125,7 +126,7 @@ const esmockModulesCreate = async (pathCallee, pathModule, esmockKey, defs, keys
return mocks

let mockedPathFull = resolvewith(keys[0], pathCallee)
if (!mockedPathFull && opt.isPackageNotFoundError === false) {
if (!mockedPathFull && opt.isModuleNotFoundError === false) {
mockedPathFull = 'file:///' + keys[0]
opt = Object.assign({ isfound: false }, opt)
}
Expand Down
22 changes: 11 additions & 11 deletions tests/tests-ava/spec/esmock.ava.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sinon from 'sinon'
test('should not error when handling non-extensible object', async t => {
// if esmock tries to simulate babel and define default.default
// runtime error may occur if non-extensible is defined there
await esmock.partial('../../local/importsNonDefaultClass.js', {
await esmock('../../local/importsNonDefaultClass.js', {
'../../local/exportsNonDefaultClass.js': {
getNotifier: {
default: class getNotifier {
Expand All @@ -18,9 +18,9 @@ test('should not error when handling non-extensible object', async t => {
// this error can also occur when an esmocked module is used to
// mock antother module, where esmock defined default.default on the first
// module and tried to define again from the outer module
const mockedIndex = await esmock.partial(
const mockedIndex = await esmock(
'../../local/importsNonDefaultClass.js', {
'../../local/exportsNonDefaultClass.js': await esmock.partial(
'../../local/exportsNonDefaultClass.js': await esmock(
'../../local/exportsNonDefaultClass.js', {
'../../local/pathWrap.js': {
basename: () => 'mocked basename'
Expand All @@ -43,7 +43,7 @@ test('should return un-mocked file', async t => {
})

test('should mock a local file', async t => {
const main = await esmock.partial('../../local/main.js', {
const main = await esmock('../../local/main.js', {
'../../local/mainUtil.js': {
createString: () => 'test string'
}
Expand Down Expand Up @@ -166,7 +166,7 @@ test('should return un-mocked file (again)', async t => {
})

test('should mock local file', async t => {
const mainUtil = await esmock.partial('../../local/mainUtil.js', {
const mainUtil = await esmock('../../local/mainUtil.js', {
'../../local/mainUtilNamedExports.js': {
mainUtilNamedExportOne: () => 'foobar'
}
Expand All @@ -182,7 +182,7 @@ test('should mock local file', async t => {
})

test('should mock module and local file at the same time', async t => {
const mainUtil = await esmock.partial('../../local/mainUtil.js', {
const mainUtil = await esmock('../../local/mainUtil.js', {
'form-urlencoded': o => JSON.stringify(o),
'../../local/mainUtilNamedExports.js': {
mainUtilNamedExportOne: () => 'foobar'
Expand All @@ -197,7 +197,7 @@ test('should mock module and local file at the same time', async t => {
})

test('__esModule definition, inconsequential', async t => {
const mainUtil = await esmock.partial('../../local/mainUtil.js', {
const mainUtil = await esmock('../../local/mainUtil.js', {
'babelGeneratedDoubleDefault': o => o,
'../../local/mainUtilNamedExports.js': {
mainUtilNamedExportOne: () => 'foobar',
Expand All @@ -209,7 +209,7 @@ test('__esModule definition, inconsequential', async t => {
})

test('should work well with sinon', async t => {
const mainUtil = await esmock.partial('../../local/mainUtil.js', {
const mainUtil = await esmock('../../local/mainUtil.js', {
'../../local/mainUtilNamedExports.js': {
mainUtilNamedExportOne: sinon.stub().returns('foobar')
}
Expand Down Expand Up @@ -262,7 +262,7 @@ test('should mock core module', async t => {
})

test('should apply third parameter "global" definitions', async t => {
const main = await esmock.partial('../../local/main.js', {
const main = await esmock('../../local/main.js', {
'../../local/mainUtil.js': {
exportedFunction: () => 'foobar'
}
Expand Down Expand Up @@ -334,7 +334,7 @@ test('should have small querystring in stacktrace filename', async t => {
test('should have small querystring in stacktrace filename, deep', async t => {
const {
causeRuntimeErrorFromImportedFile
} = await esmock.partial('../../local/main.js', {}, {
} = await esmock('../../local/main.js', {}, {
'../../local/mainUtil.js': {
causeRuntimeError: () => {
t.nonexistantmethod()
Expand All @@ -355,7 +355,7 @@ test('should have small querystring in stacktrace filename, deep', async t => {

test('should have small querystring in stacktrace filename, deep2', async t => {
const causeDeepErrorParent =
await esmock.partial('../../local/causeDeepErrorParent.js', {}, {
await esmock('../../local/causeDeepErrorParent.js', {}, {
'../../local/causeDeepErrorGrandChild.js': {
what: 'now'
}
Expand Down
2 changes: 1 addition & 1 deletion tests/tests-no-loader/esmock.loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
import esmock from 'esmock'

test('should throw error if !esmockloader', async () => {
const main = await esmock.partial('../local/main.js', {
const main = await esmock('../local/main.js', {
'../local/mainUtil.js': {
createString: () => 'test string'
}
Expand Down
10 changes: 3 additions & 7 deletions tests/tests-node/esmock.node.importing.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import esmock, { partial, strict } from 'esmock'
import esmock, { strict } from 'esmock'

const isPassingPartial = async esmockPartial => {
const main = await esmockPartial('../local/main.js', {
Expand All @@ -27,17 +27,13 @@ const isPassingStrict = async esmockStrict => {
}

test('should export esmock partial', async () => {
await isPassingPartial(partial)
await isPassingPartial(partial.p)
await isPassingPartial(esmock.partial)
await isPassingPartial(esmock.partial.p)
await isPassingPartial(esmock)
await isPassingPartial(esmock.p)
})

test('should export esmock strict', async () => {
await isPassingStrict(strict)
await isPassingStrict(strict.p)
await isPassingStrict(esmock.strict)
await isPassingStrict(esmock.strict.p)
await isPassingStrict(esmock)
await isPassingStrict(esmock.p)
})
Loading

0 comments on commit 302ca82

Please sign in to comment.