Comparing version 0.5.7 to 0.6.0
var Module = require('module') | ||
var path = require('path') | ||
var { URL } = require('url') | ||
var resolve = require('resolve') | ||
@@ -12,2 +13,3 @@ var _ = { | ||
invokeMap: require('lodash/fp/invokeMap'), | ||
map: require('lodash/fp/map'), | ||
includes: require('lodash/fp/includes'), | ||
@@ -20,2 +22,3 @@ reject: require('lodash/fp/reject'), | ||
} | ||
var importFunctionsModule | ||
@@ -35,2 +38,3 @@ var originalLoad = Module._load | ||
} | ||
return quibbles[request].stub | ||
@@ -57,2 +61,7 @@ } | ||
quibbles = {} | ||
if (global.__quibble) { | ||
delete global.__quibble.quibbledModules | ||
} | ||
config = quibble.config() | ||
@@ -73,2 +82,50 @@ if (hard) { | ||
quibble.esm = async function (importPath, namedExportStubs, defaultExportStub) { | ||
if (!global.__quibble) { global.__quibble = { quibbledModules: new Map(), stubModuleGeneration: 1 } } | ||
if (!global.__quibble.quibbledModules) { | ||
global.__quibble.quibbledModules = new Map() | ||
++global.__quibble.stubModuleGeneration | ||
} | ||
importFunctionsModule = importFunctionsModule || require('./esm-import-functions') | ||
const importPathIsBareSpecifier = isBareSpecifier(importPath) | ||
const isAbsolutePath = path.isAbsolute(importPath) | ||
const callerFile = isAbsolutePath || importPathIsBareSpecifier ? undefined : hackErrorStackToGetCallerFile() | ||
const fullModulePath = importPathIsBareSpecifier | ||
? await importFunctionsModule.dummyImportModuleToGetAtPath(importPath) | ||
: isAbsolutePath | ||
? importPath | ||
: path.resolve(path.dirname(callerFile), importPath) | ||
global.__quibble.quibbledModules.set(fullModulePath, { | ||
defaultExportStub, | ||
namedExportStubs | ||
}) | ||
} | ||
quibble.esmImportWithPath = async function esmImportWithPath (importPath) { | ||
const importPathIsBareSpecifier = isBareSpecifier(importPath) | ||
const isAbsolutePath = path.isAbsolute(importPath) | ||
const callerFile = isAbsolutePath || importPathIsBareSpecifier ? undefined : hackErrorStackToGetCallerFile() | ||
importFunctionsModule = importFunctionsModule || require('./esm-import-functions') | ||
const modulePath = importPathIsBareSpecifier | ||
? await importFunctionsModule.dummyImportModuleToGetAtPath(importPath) | ||
: isAbsolutePath | ||
? importPath | ||
: path.resolve(path.dirname(callerFile), importPath) | ||
const fullImportPath = importPathIsBareSpecifier | ||
? importPath | ||
: modulePath | ||
return { | ||
modulePath, | ||
module: await importFunctionsModule.importOriginalModule(fullImportPath) | ||
} | ||
} | ||
var absolutePathFor = function (relativePath, parentFileName) { | ||
@@ -148,3 +205,5 @@ if (_.startsWith(relativePath, '/') || /^(\w|@)/.test(relativePath)) { | ||
var originalFunc = Error.prepareStackTrace | ||
var originalStackTraceLimit = Error.stackTraceLimit | ||
try { | ||
Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 30) | ||
Error.prepareStackTrace = function (e, stack) { | ||
@@ -154,6 +213,7 @@ return stack | ||
var e = new Error() | ||
var currentFile = e.stack[0].getFileName() | ||
var currentFile = convertUrlToPath(e.stack[0].getFileName()) | ||
return _.flow([ | ||
_.invokeMap('getFileName'), | ||
_.compact, | ||
_.map(convertUrlToPath), | ||
_.reject(function (f) { | ||
@@ -169,3 +229,42 @@ return includeGlobalIgnores && _.includes(f, ignoredCallerFiles) | ||
Error.prepareStackTrace = originalFunc | ||
Error.stackTraceLimit = originalStackTraceLimit | ||
} | ||
} | ||
function convertUrlToPath (fileUrl) { | ||
try { | ||
const p = new URL(fileUrl).pathname | ||
return p | ||
} catch (error) { | ||
if (error.code === 'ERR_INVALID_URL') { | ||
return fileUrl | ||
} else { | ||
throw error | ||
} | ||
} | ||
} | ||
function isBareSpecifier (modulePath) { | ||
const firstLetter = modulePath[0] | ||
if (firstLetter === '.' || firstLetter === '/') { | ||
return false | ||
} | ||
if (!modulePath.includes(':')) { | ||
return true | ||
} | ||
try { | ||
// (yes, we DO use new for side-effects!) | ||
// eslint-disable-next-line | ||
new URL(modulePath) | ||
} catch (error) { | ||
if (error.code === 'ERR_INVALID_URL') { | ||
return false | ||
} else { | ||
throw error | ||
} | ||
} | ||
return true | ||
} |
{ | ||
"name": "quibble", | ||
"version": "0.5.7", | ||
"version": "0.6.0", | ||
"description": "Makes it easy to replace require'd dependencies.", | ||
"homepage": "https://github.com/testdouble/quibble", | ||
"main": "./index.js", | ||
"exports": { | ||
".": { | ||
"require": "./lib/quibble.js", | ||
"import": "./lib/quibble.mjs" | ||
}, | ||
"./": "./" | ||
}, | ||
"scripts": { | ||
"test": "teenytest", | ||
"style": "standard --fix", | ||
"test:esm": "if node test/supports-esm.js; then NODE_OPTIONS=--loader=quibble ./test/esm-lib/teenytest-proxy.js './test/esm-lib/*.test.{mjs,js}'; fi", | ||
"test:example": "cd example && npm it", | ||
"test:example-esm": "if node test/supports-esm.js; then node test/supports-esm.js && cd example && npm it; fi", | ||
"test:smells": "./test/require-smell-test.sh", | ||
"test:ci": "npm test && npm run style && npm run test:example && npm run test:smells", | ||
"test:ci": "npm test && npm run test:esm && npm run style && npm run test:example && npm run test:example-esm && npm run test:smells", | ||
"preversion": "git pull --rebase && npm run test:ci", | ||
@@ -27,4 +37,6 @@ "postversion": "git push && git push --tags && npm publish" | ||
"is-number": "^7.0.0", | ||
"standard": "^13.0.2", | ||
"teenytest": "^5.2.0" | ||
"is-promise": "^4.0.0", | ||
"standard": "^14.3.3", | ||
"teenytest": "^6.0.1", | ||
"teenytest-promise": "^1.0.0" | ||
}, | ||
@@ -36,5 +48,11 @@ "standard": { | ||
"ignore": [ | ||
"example" | ||
"example", | ||
"example-esm" | ||
] | ||
}, | ||
"teenytest": { | ||
"plugins": [ | ||
"teenytest-promise" | ||
] | ||
}, | ||
"bugs": { | ||
@@ -41,0 +59,0 @@ "url": "https://github.com/testdouble/quibble/issues" |
@@ -69,2 +69,71 @@ # quibble | ||
> Note: `defaultFakeCreator` is not supported for ES Module stubbing | ||
## ES Modules support | ||
Quibble supports ES Modules. Quibble implements ES module support using [ES Module | ||
Loaders](https://nodejs.org/api/esm.html#esm_experimental_loaders) which are the official way to | ||
"patch" Node.js' module loading mechanism for ESM. | ||
> Note that Loader support is currently experimental and unstable. We will be doing our best | ||
to track the changes in the specification for the upcoming Node.js versions. Also note that | ||
Quibble ESM support is tested only for versions 13 and above. | ||
To use Quibble support, you must run Node with the `quibble` package as the loader: | ||
```sh | ||
node --loader=quibble ... | ||
``` | ||
Most test runners allow you to specify this in their command line, e.g. for Mocha: | ||
```sh | ||
mocha --loader=quibble ... | ||
``` | ||
The `quibble` loader will enable the replacement of the ES modules with the stubs you specify, and | ||
without it, the stubbing will be ignored. | ||
### Restrictions on ESM | ||
* `defaultFakeCreator` is not yet supported. | ||
### `quibble` ESM API | ||
The API is similar to the CommonJS API, but uses `quibble.esm` function, and is async: | ||
```js | ||
// a-module.mjs (ESM) | ||
export const life = 42; | ||
export default 'universe'; | ||
// uses-a-module.mjs | ||
import universe, {life} from './a-module.mjs'; | ||
console.log(life, universe); | ||
(async function () { | ||
await quibble.esm('./a-module.mjs', {life: 41}, 'replacement universe'); | ||
await import('./uses-some-module.mjs'); | ||
// ==> logs: 41, replacement universe | ||
})(); | ||
``` | ||
The parameters to `quibble` for ESM modules are: | ||
1. the module path: similar to CommonJS, the path is relative to the directory you are in. It is | ||
resolved the ESM way, so if you're using a relative path, you must specify the filename, | ||
including the extension. | ||
* `quibble.reset` works the same as for CommonJS modules | ||
ESM support also exposes the function `quibble.esmImportWithPath` which both imports a module and | ||
resolves the path to the module that is the package's entry point: | ||
* `async quibble.esmImportWithPath(importPath)`: imports a module, just like `import(importPath)`, | ||
but returns an object with two properties: | ||
* `module`: the module returned by `await import(importPath)`. | ||
* `modulePath`: the full path to the module (file) that is the entry point to the package/module. | ||
## How's it different? | ||
@@ -71,0 +140,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
203037
44
4684
153
6
1