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

Add overloads for mock functions, fully expand out d.ts file #122

Merged
merged 16 commits into from
Aug 21, 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# changelog

* 1.9.5
* 1.9.6 _Aug.19.2022_
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This date's now wrong; feel free to change.

* support parent url to facilitate sourcemap usage, [113](https://github.com/iambumblehead/esmock/issues/113)
* 1.9.5 _Aug.19.2022_
* support cjs packges that define [main relative directory only](https://github.com/iambumblehead/esmock/issues/119)
* 1.9.4 _Aug.15.2022_
* support core modules [w/ node: prefix](https://github.com/iambumblehead/resolvewithplus/pull/27), credit @gmahomarf
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"test-ci": "npm run test:install && npm run test:all-ci",
"test-cover": "npm run test:install && c8 npm run test:all",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"mini:pkg": "npm pkg delete scripts devDependencies",
"prepublishOnly": "npm run lint && npm run test-ci && npm run mini:pkg"
}
Expand Down
76 changes: 75 additions & 1 deletion src/esmock.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,76 @@
declare function esmock(path: string, localmock?: any, globalmock?: any): any;
/**
* Mocks imports for the module specified by {@link modulePath}.
*
* The provided mocks replace the imported modules _fully_.
*
* @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)`.
*/
declare function esmock(modulePath: string, parent: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
declare function esmock(modulePath: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;

declare namespace esmock {
interface Options {
partial?: boolean | undefined;
purge?: boolean | undefined;
Comment on lines +23 to +24
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be documented; not sure quite what to write as personally I would suggest that people use the named functions to set these instead (i.e. treat options as an internal implementation detail).

}

/**
* Mocks imports for the module specified by {@link modulePath}.
*
* The provided mocks replace the imported modules _partially_, allowing some exports to
* be overridden while the rest are provided by the real module.
*
* @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 px(modulePath: string, parent: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;
function px(modulePath: string, mockDefs?: Record<string, any>, globalDefs?: Record<string, any>, opt?: esmock.Options): any;

/**
* Mocks dynamic imports for the module specified by {@link modulePath}.
*
* After using this function, consider calling {@link esmock.purge} to free memory.
*
* @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 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;

/**
* Unregisters a dynamic mock created by {@link esmock.p}.
*
* @param mockModule A module object that was previously returned by {@link esmock.p}.
*/
function purge(mockModule: any): void;
}

export default esmock;
33 changes: 27 additions & 6 deletions src/esmock.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,21 @@ import {
esmockCache
} from './esmockCache.js'

const esmock = async (modulePath, mockDefs, globalDefs, opt = {}, err) => {
const calleePath = (err || new Error).stack.split('\n')[2]
const argsToObj = args => {
// Distinguish between the two overloads; see esmock.d.ts.
let modulePath, parent, mockDefs, globalDefs, opt
if (typeof args[1] === "string") {
[ modulePath, parent, mockDefs, globalDefs, opt ] = args
} else {
[ modulePath, mockDefs, globalDefs, opt ] = args
}
return { modulePath, parent, mockDefs, globalDefs, opt }
}

const _esmock = async (argsObj, err) => {
const { modulePath, parent, mockDefs, globalDefs, opt = {} } = argsObj

const calleePath = (parent || err.stack.split('\n')[2])
.replace(/^.*file:\/\//, '') // rm every before filepath
.replace(/:[\d]*:[\d]*.*$/, '') // rm line and row number
.replace(/^.*:/, '') // rm windows-style drive location
Expand All @@ -29,11 +42,19 @@ const esmock = async (modulePath, mockDefs, globalDefs, opt = {}, err) => {
return esmockModuleImportedSanitize(importedModule, modulePathKey)
}

esmock.px = async (modulePath, mockDefs, globalDefs, opt) => esmock(
modulePath, mockDefs, globalDefs, { ...opt, partial: true }, new Error)
const esmock = async (...args) => _esmock(argsToObj(args), new Error)

esmock.p = async (modulePath, mockDefs, globalDefs, opt) => esmock(
modulePath, mockDefs, globalDefs, { ...opt, purge: false }, new Error)
esmock.px = async (...args) => {
const argsObj = argsToObj(args)
argsObj.opt = { ...argsObj.opt, partial: true }
return _esmock(argsObj, new Error)
}

esmock.p = async (...args) => {
const argsObj = argsToObj(args)
argsObj.opt = { ...argsObj.opt, purge: false }
return _esmock(argsObj, new Error)
}

esmock.purge = mockModule => {
if (mockModule && /object|function/.test(typeof mockModule)
Expand Down
6 changes: 6 additions & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"url": "https://github.com/iambumblehead/esmock.git"
},
"main": "package.json.esmock.export.js",
"exports": {
"types": "./package.json.esmock.export.d.ts",
"import": "./package.json.esmock.export.js"
},
"dependencies": {
"pg": "^8.7.3",
"eslint": "^8.12.0",
Expand All @@ -27,6 +31,7 @@
"install:test-tsm": "cd tests-tsm && npm install",
"install:test-node": "cd tests-node && npm install",
"install:test-jest": "cd tests-jest && npm install",
"install:test-source-map": "cd tests-source-map && npm install",
"install:test-nodets": "cd tests-nodets && npm install",
"install:all": "node --version && npm install && npm-run-all install:test*",
"test:test-ava": "cd tests-ava && npm test",
Expand All @@ -35,6 +40,7 @@
"test:node18-test-node": "cd tests-node && npm test",
"test:node18-test-jest": "cd tests-jest && npm test",
"test:node18-test-nodets": "cd tests-nodets && npm test",
"test:node18-test-source-map": "cd tests-source-map && npm test",
"test:node18:all": "npm run isnodelt18 || npm-run-all test:node18-test*",
"test:all": "npm-run-all test:test* && npm run test:node18:all",
"test:all-cover": "c8 --src=../src/* npm run test:all",
Expand Down
3 changes: 3 additions & 0 deletions tests/package.json.esmock.export.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import esmock from "../src/esmock.js";

export default esmock;
1 change: 1 addition & 0 deletions tests/tests-source-map/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
7 changes: 7 additions & 0 deletions tests/tests-source-map/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# esmock-source-map-bug

To run with source maps, run `npm run test`. esmock would previously fail unless the provided module path was absolute

To run without source maps, run `npm run test-no-maps`. This works.

https://github.com/iambumblehead/esmock/issues/113
34 changes: 34 additions & 0 deletions tests/tests-source-map/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"type": "module",
"description": "esmock unit tests, ava with sourcemap",
"repository": {
"type": "git",
"url": "https://github.com/iambumblehead/esmock.git"
},
"dependencies": {
"esmock": "file:..",
"@ava/typescript": "^3.0.1",
"@tsconfig/node14": "^1.0.3",
"@types/node": "^18.7.3",
"ava": "^4.3.1",
"cross-env": "^7.0.3",
"rimraf": "^3.0.2",
"typescript": "^4.7.4"
},
"scripts": {
"test": "rimraf dist && tsc && cross-env NODE_OPTIONS=--loader=esmock NODE_NO_WARNINGS=1 ava",
"test-no-maps": "rimraf dist && tsc --sourceMap false && cross-env NODE_OPTIONS=--loader=esmock NODE_NO_WARNINGS=1 ava"
},
"ava": {
"typescript": {
"rewritePaths": {
"src/": "dist/"
},
"compile": false
},
"environmentVariables": {
"NO_COLOR": "1",
"FORCE_COLOR": "0"
}
}
}
30 changes: 30 additions & 0 deletions tests/tests-source-map/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import test from "ava";
import esmock from "esmock";
import { fileURLToPath } from "url";

import type * as indexType from "../index.js";

const expectedHostname = "my-machine";

test("using absolute path", async (t) => {
const urlFull = fileURLToPath(new URL("../index.js", import.meta.url));
const indexModule = await esmock(urlFull, {
os: {
hostname: () => expectedHostname,
},
});

const getHostname: typeof indexType.getHostname = indexModule.getHostname;
t.is(getHostname(), expectedHostname);
});

test("using relative path", async (t) => {
const indexModule = await esmock("../index.js", import.meta.url, {
os: {
hostname: () => expectedHostname,
},
});

const getHostname: typeof indexType.getHostname = indexModule.getHostname;
t.is(getHostname(), expectedHostname);
});
5 changes: 5 additions & 0 deletions tests/tests-source-map/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import os from "os";

export function getHostname() {
return os.hostname();
}
23 changes: 23 additions & 0 deletions tests/tests-source-map/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extends": "@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"module": "Node16",
"moduleResolution": "Node16",
"rootDir": "src",
"outDir": "dist",
"declarationDir": "dist",
"declaration": true,
"stripInternal": true,
"sourceMap": true,

"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"importsNotUsedAsValues": "error"
},
"exclude": ["dist"]
}