Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use partial variant as default export #141

Merged
merged 6 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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