diff --git a/docs/en/Configuration.md b/docs/en/Configuration.md index 1aabb4862fee..f2e556d055ec 100644 --- a/docs/en/Configuration.md +++ b/docs/en/Configuration.md @@ -369,7 +369,8 @@ This option allows the use of a custom resolver. This resolver must be a node mo "browser": bool, "extensions": [string], "moduleDirectory": [string], - "paths": [string] + "paths": [string], + "rootDir": [string] } ``` diff --git a/integration_tests/__tests__/__snapshots__/module_name_mapper.test.js.snap b/integration_tests/__tests__/__snapshots__/module_name_mapper.test.js.snap index dfd436b63467..0d87e8bcdf33 100644 --- a/integration_tests/__tests__/__snapshots__/module_name_mapper.test.js.snap +++ b/integration_tests/__tests__/__snapshots__/module_name_mapper.test.js.snap @@ -13,12 +13,14 @@ exports[`moduleNameMapper wrong configuration 1`] = ` Configuration error: - Unknown module in configuration option moduleNameMapper + Could not locate module ./style.css (mapped as no-such-module) + Please check: \\"moduleNameMapper\\": { \\"/\\\\.(css|less)$/\\": \\"no-such-module\\" - } + }, + \\"resolver\\": undefined " `; diff --git a/packages/jest-cli/src/reporters/default_reporter.js b/packages/jest-cli/src/reporters/default_reporter.js index ded771dbffb2..64093c91652c 100644 --- a/packages/jest-cli/src/reporters/default_reporter.js +++ b/packages/jest-cli/src/reporters/default_reporter.js @@ -183,7 +183,7 @@ class DefaultReporter extends BaseReporter { TITLE_BULLET + 'Console\n\n' + getConsoleOutput( - config.rootDir, + config.cwd, !!this._globalConfig.verbose, consoleBuffer, ), diff --git a/packages/jest-cli/src/reporters/utils.js b/packages/jest-cli/src/reporters/utils.js index b7b440f8e572..b3d6b729696c 100644 --- a/packages/jest-cli/src/reporters/utils.js +++ b/packages/jest-cli/src/reporters/utils.js @@ -8,7 +8,7 @@ * @flow */ -import type {Path, ProjectConfig} from 'types/Config'; +import type {Path, ProjectConfig, GlobalConfig} from 'types/Config'; import type {AggregatedResult} from 'types/TestResult'; import path from 'path'; @@ -37,7 +37,7 @@ const printDisplayName = (config: ProjectConfig) => { const trimAndFormatPath = ( pad: number, - config: {rootDir: Path}, + config: ProjectConfig | GlobalConfig, testPath: Path, columns: number, ): string => { @@ -72,13 +72,19 @@ const trimAndFormatPath = ( ); }; -const formatTestPath = (config: {rootDir: Path}, testPath: Path) => { +const formatTestPath = ( + config: GlobalConfig | ProjectConfig, + testPath: Path, +) => { const {dirname, basename} = relativePath(config, testPath); return chalk.dim(dirname + path.sep) + chalk.bold(basename); }; -const relativePath = (config: {rootDir: Path}, testPath: Path) => { - testPath = path.relative(config.rootDir, testPath); +const relativePath = (config: GlobalConfig | ProjectConfig, testPath: Path) => { + // this function can be called with ProjectConfigs or GlobalConfigs. GlobalConfigs + // do not have config.cwd, only config.rootDir. Try using config.cwd, fallback + // to config.rootDir. (Also, some unit just use config.rootDir, which is ok) + testPath = path.relative(config.cwd || config.rootDir, testPath); const dirname = path.dirname(testPath); const basename = path.basename(testPath); return {basename, dirname}; diff --git a/packages/jest-cli/src/run_jest.js b/packages/jest-cli/src/run_jest.js index 9420c3674b33..f0c7573c1f9c 100644 --- a/packages/jest-cli/src/run_jest.js +++ b/packages/jest-cli/src/run_jest.js @@ -140,10 +140,13 @@ const runJest = async ({ } // When using more than one context, make all printed paths relative to the - // current cwd. rootDir is only used as a token during normalization and - // has no special meaning afterwards except for printing information to the - // CLI. - setConfig(contexts, {rootDir: process.cwd()}); + // current cwd. Do not modify rootDir, since will be used by custom resolvers. + // If --runInBand is true, the resolver saved a copy during initialization, + // however, if it is running on spawned processes, the initiation of the + // custom resolvers is done within each spawned process and it needs the + // original value of rootDir. Instead, use the {cwd: Path} property to resolve + // paths when printing. + setConfig(contexts, {cwd: process.cwd()}); const results = await new TestScheduler(globalConfig, { startRun, diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index c92558f6fba5..e5a5b698c113 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -120,6 +120,7 @@ const getConfigs = ( cacheDirectory: options.cacheDirectory, clearMocks: options.clearMocks, coveragePathIgnorePatterns: options.coveragePathIgnorePatterns, + cwd: options.cwd, displayName: options.displayName, globals: options.globals, haste: options.haste, diff --git a/packages/jest-resolve/src/__tests__/resolve.test.js b/packages/jest-resolve/src/__tests__/resolve.test.js index 1d97a3c9f550..6140038d52cc 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.js +++ b/packages/jest-resolve/src/__tests__/resolve.test.js @@ -9,10 +9,17 @@ 'use strict'; +jest.mock('../__mocks__/userResolver'); + const fs = require('fs'); const path = require('path'); const ModuleMap = require('jest-haste-map').ModuleMap; const Resolver = require('../'); +const userResolver = require('../__mocks__/userResolver'); + +beforeEach(() => { + userResolver.mockClear(); +}); describe('isCoreModule', () => { it('returns false if `hasCoreModules` is false.', () => { @@ -50,8 +57,6 @@ describe('findNodeModule', () => { .map(p => path.resolve(resolvedCwd, p)) : null; - jest.mock('../__mocks__/userResolver'); - const userResolver = require('../__mocks__/userResolver'); userResolver.mockImplementation(() => 'module'); const newPath = Resolver.findNodeModule('test', { @@ -74,3 +79,54 @@ describe('findNodeModule', () => { }); }); }); + +describe('getMockModule', () => { + it('is possible to use custom resolver to resolve deps inside mock modules with moduleNameMapper', () => { + userResolver.mockImplementation(() => 'module'); + + const moduleMap = new ModuleMap({ + duplicates: [], + map: [], + mocks: [], + }); + const resolver = new Resolver(moduleMap, { + moduleNameMapper: [ + { + moduleName: '$1', + regex: /(.*)/, + }, + ], + resolver: require.resolve('../__mocks__/userResolver'), + }); + const src = require.resolve('../'); + resolver.getMockModule(src, 'dependentModule'); + + expect(userResolver).toHaveBeenCalled(); + expect(userResolver.mock.calls[0][0]).toBe('dependentModule'); + expect(userResolver.mock.calls[0][1]).toHaveProperty( + 'basedir', + path.dirname(src), + ); + }); + it('is possible to use custom resolver to resolve deps inside mock modules without moduleNameMapper', () => { + userResolver.mockImplementation(() => 'module'); + + const moduleMap = new ModuleMap({ + duplicates: [], + map: [], + mocks: [], + }); + const resolver = new Resolver(moduleMap, { + resolver: require.resolve('../__mocks__/userResolver'), + }); + const src = require.resolve('../'); + resolver.getMockModule(src, 'dependentModule'); + + expect(userResolver).toHaveBeenCalled(); + expect(userResolver.mock.calls[0][0]).toBe('dependentModule'); + expect(userResolver.mock.calls[0][1]).toHaveProperty( + 'basedir', + path.dirname(src), + ); + }); +}); diff --git a/packages/jest-resolve/src/default_resolver.js b/packages/jest-resolve/src/default_resolver.js index 2a48307cf5eb..780505ebe924 100644 --- a/packages/jest-resolve/src/default_resolver.js +++ b/packages/jest-resolve/src/default_resolver.js @@ -18,6 +18,7 @@ type ResolverOptions = {| extensions?: Array, moduleDirectory?: Array, paths?: ?Array, + rootDir: ?Path, |}; function defaultResolver(path: Path, options: ResolverOptions): Path { @@ -28,6 +29,7 @@ function defaultResolver(path: Path, options: ResolverOptions): Path { extensions: options.extensions, moduleDirectory: options.moduleDirectory, paths: options.paths, + rootDir: options.rootDir, }); } diff --git a/packages/jest-resolve/src/index.js b/packages/jest-resolve/src/index.js index 90565d999210..8de6a21bca77 100644 --- a/packages/jest-resolve/src/index.js +++ b/packages/jest-resolve/src/index.js @@ -29,6 +29,7 @@ type ResolverConfig = {| modulePaths: Array, platforms?: Array, resolver: ?Path, + rootDir: ?Path, |}; type FindNodeModuleConfig = {| @@ -38,6 +39,7 @@ type FindNodeModuleConfig = {| moduleDirectory?: Array, paths?: Array, resolver?: ?Path, + rootDir?: ?Path, |}; type ModuleNameMapperConfig = {| @@ -79,6 +81,7 @@ class Resolver { modulePaths: options.modulePaths, platforms: options.platforms, resolver: options.resolver, + rootDir: options.rootDir, }; this._moduleMap = moduleMap; this._moduleIDCache = Object.create(null); @@ -100,6 +103,7 @@ class Resolver { extensions: options.extensions, moduleDirectory: options.moduleDirectory, paths: paths ? (nodePaths || []).concat(paths) : nodePaths, + rootDir: options.rootDir, }); } catch (e) {} return null; @@ -152,6 +156,7 @@ class Resolver { moduleDirectory, paths, resolver: this._options.resolver, + rootDir: this._options.rootDir, }); }; @@ -316,39 +321,46 @@ class Resolver { const extensions = this._options.extensions; const moduleDirectory = this._options.moduleDirectories; const moduleNameMapper = this._options.moduleNameMapper; + const resolver = this._options.resolver; if (moduleNameMapper) { for (const {moduleName: mappedModuleName, regex} of moduleNameMapper) { if (regex.test(moduleName)) { + // Note: once a moduleNameMapper matches the name, it must result + // in a module, or else an error is thrown. const matches = moduleName.match(regex); - if (!matches) { - moduleName = mappedModuleName; - } else { - moduleName = mappedModuleName.replace( - /\$([0-9]+)/g, - (_, index) => matches[parseInt(index, 10)], - ); - } + const updatedName = matches + ? mappedModuleName.replace( + /\$([0-9]+)/g, + (_, index) => matches[parseInt(index, 10)], + ) + : mappedModuleName; const module = - this.getModule(moduleName) || - Resolver.findNodeModule(moduleName, { + this.getModule(updatedName) || + Resolver.findNodeModule(updatedName, { basedir: dirname, browser: this._options.browser, extensions, moduleDirectory, paths, + resolver, + rootDir: this._options.rootDir, }); if (!module) { const error = new Error( chalk.red(`${chalk.bold('Configuration error')}: -Unknown module in configuration option ${chalk.bold('moduleNameMapper')} +Could not locate module ${chalk.bold(moduleName)} (mapped as ${chalk.bold( + updatedName, + )}) + Please check: "moduleNameMapper": { - "${regex.toString()}": "${chalk.bold(moduleName)}" -}`), + "${regex.toString()}": "${chalk.bold(mappedModuleName)}" +}, +"resolver": ${chalk.bold(resolver)}`), ); error.stack = ''; throw error; @@ -357,6 +369,22 @@ Please check: } } } + if (resolver) { + // if moduleNameMapper didn't match anything, fallback to just the + // regular resolver + const module = + this.getModule(moduleName) || + Resolver.findNodeModule(moduleName, { + basedir: dirname, + browser: this._options.browser, + extensions, + moduleDirectory, + paths, + resolver, + rootDir: this._options.rootDir, + }); + return module; + } return null; } diff --git a/packages/jest-runner/src/run_test.js b/packages/jest-runner/src/run_test.js index b1e9a2d2869f..7941061ba293 100644 --- a/packages/jest-runner/src/run_test.js +++ b/packages/jest-runner/src/run_test.js @@ -70,7 +70,7 @@ function runTest( const consoleOut = globalConfig.useStderr ? process.stderr : process.stdout; const consoleFormatter = (type, message) => getConsoleOutput( - config.rootDir, + config.cwd, !!globalConfig.verbose, // 4 = the console call is buried 4 stack frames deep BufferedConsole.write([], type, message, 4), diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 51b54ece9f4d..7a77ca39849c 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -259,6 +259,7 @@ class Runtime { modulePaths: config.modulePaths, platforms: config.haste.platforms, resolver: config.resolver, + rootDir: config.rootDir, }); } diff --git a/test_utils.js b/test_utils.js index 45c5aef89582..280b758c9db0 100644 --- a/test_utils.js +++ b/test_utils.js @@ -60,6 +60,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { cacheDirectory: '/test_cache_dir/', clearMocks: false, coveragePathIgnorePatterns: [], + cwd: '/test_root_dir/', displayName: undefined, globals: {}, haste: { diff --git a/types/Config.js b/types/Config.js index 33e809f8d7a7..4c5e831f3d20 100644 --- a/types/Config.js +++ b/types/Config.js @@ -188,6 +188,7 @@ export type ProjectConfig = {| cacheDirectory: Path, clearMocks: boolean, coveragePathIgnorePatterns: Array, + cwd: Path, displayName: ?string, globals: ConfigGlobals, haste: HasteConfig,