Skip to content
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
66 changes: 49 additions & 17 deletions lib/proxyquire.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
/* jshint laxbreak:true, loopfunc:true */

var Module = require('module')
var path = require('path')
var resolve = require('resolve')
var dirname = require('path').dirname
var ProxyquireError = require('./proxyquire-error')
var is = require('./is')
var assert = require('assert')
Expand Down Expand Up @@ -129,13 +129,47 @@ Proxyquire.prototype.load = function (request, stubs) {
return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request))
}

// Resolves a stub relative to a module.
// `baseModule` is the module we're resolving from. `pathToResolve` is the
// module we want to resolve (i.e. the string passed to `require()`).
Proxyquire.prototype._resolveModule = function (baseModule, pathToResolve) {
try {
return resolve.sync(pathToResolve, {
basedir: path.dirname(baseModule),
extensions: Object.keys(require.extensions),
paths: Module.globalPaths
})
} catch (err) {
// If this is not a relative path (e.g. "foo" as opposed to "./foo"), and
// we couldn't resolve it, then we just let the path through unchanged.
// It's safe to do this, because if two different modules require "foo",
// they both expect to get back the same thing.
if (pathToResolve[0] !== '.') {
return pathToResolve
}

// If `pathToResolve` is relative, then it is *not* safe to return it,
// since a file in one directory that requires "./foo" expects to get
// back a different module than one that requires "./foo" from another
// directory. However, if !this._preserveCache, then we don't want to
// throw, since we can resolve modules that don't exist. Resolve as
// best we can.
if (!this._preserveCache) {
return path.resolve(path.dirname(baseModule), pathToResolve)
}

throw err
}
}

// This replaces a module's require function
Proxyquire.prototype._require = function (module, stubs, path) {
assert(typeof path === 'string', 'path must be a string')
assert(path, 'missing path')

if (hasOwnProperty.call(stubs, path)) {
var stub = stubs[path]
var resolvedPath = this._resolveModule(module.filename, path)
if (hasOwnProperty.call(stubs, resolvedPath)) {
var stub = stubs[resolvedPath]

if (stub === null) {
// Mimic the module-not-found exception thrown by node.js.
Expand Down Expand Up @@ -163,6 +197,15 @@ Proxyquire.prototype._require = function (module, stubs, path) {
Proxyquire.prototype._withoutCache = function (module, stubs, path, func) {
// Temporarily disable the cache - either per-module or globally if we have global stubs
var restoreCache = this._disableCache(module, path)
var resolvedPath = Module._resolveFilename(path, module)

// Resolve all stubs to absolute paths.
stubs = Object.keys(stubs)
.reduce(function (result, stubPath) {
var resolvedStubPath = this._resolveModule(resolvedPath, stubPath)
result[resolvedStubPath] = stubs[stubPath]
return result
}.bind(this), {})

// Override all require extension handlers
var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs)
Expand All @@ -175,18 +218,7 @@ Proxyquire.prototype._withoutCache = function (module, stubs, path, func) {
if (this._preserveCache) {
restoreCache()
} else {
var id = Module._resolveFilename(path, module)
var stubIds = Object.keys(stubs).map(function (stubPath) {
try {
return resolve.sync(stubPath, {
basedir: dirname(id),
extensions: Object.keys(require.extensions),
paths: Module.globalPaths
})
} catch (_) {}
})
var ids = [id].concat(stubIds.filter(Boolean))

var ids = [resolvedPath].concat(Object.keys(stubs).filter(Boolean))
ids.forEach(function (id) {
delete require.cache[id]
})
Expand Down Expand Up @@ -251,7 +283,7 @@ Proxyquire.prototype._disableModuleCache = function (path, module) {
}
}

Proxyquire.prototype._overrideExtensionHandlers = function (module, stubs) {
Proxyquire.prototype._overrideExtensionHandlers = function (module, resolvedStubs) {
/* eslint node/no-deprecated-api: [error, {ignoreGlobalItems: ["require.extensions"]}] */

var originalExtensions = {}
Expand All @@ -266,7 +298,7 @@ Proxyquire.prototype._overrideExtensionHandlers = function (module, stubs) {
// Override the default handler for the requested file extension
require.extensions[extension] = function (module, filename) {
// Override the require method for this module
module.require = self._require.bind(self, module, stubs)
module.require = self._require.bind(self, module, resolvedStubs)

return originalExtensions[extension](module, filename)
}
Expand Down
13 changes: 13 additions & 0 deletions test/proxyquire-relative-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

/* jshint asi:true */
/* global describe, it */

var proxyquire = require('..')

describe('When requiring relative paths, they should be relative to the proxyrequired module', function () {
it('should return the correct result', function () {
var result = proxyquire('./samples/relative-paths/a/index.js', {'./util': {c: 'c'}})
result.should.eql({a: 'a', c: 'c'})
})
})
4 changes: 4 additions & 0 deletions test/samples/relative-paths/a/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var util = require('./util')
require('../b')

module.exports = util
1 change: 1 addition & 0 deletions test/samples/relative-paths/a/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.a = 'a'
1 change: 1 addition & 0 deletions test/samples/relative-paths/b/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./util')
1 change: 1 addition & 0 deletions test/samples/relative-paths/b/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.b = 'b'