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

Allow custom resolver to be used with[out] moduleNameMapper #4174

Merged
merged 5 commits into from
Aug 31, 2017
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
3 changes: 2 additions & 1 deletion docs/en/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

"
`;
2 changes: 1 addition & 1 deletion packages/jest-cli/src/reporters/default_reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class DefaultReporter extends BaseReporter {
TITLE_BULLET +
'Console\n\n' +
getConsoleOutput(
config.rootDir,
config.cwd,
!!this._globalConfig.verbose,
consoleBuffer,
),
Expand Down
16 changes: 11 additions & 5 deletions packages/jest-cli/src/reporters/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -37,7 +37,7 @@ const printDisplayName = (config: ProjectConfig) => {

const trimAndFormatPath = (
pad: number,
config: {rootDir: Path},
config: ProjectConfig | GlobalConfig,
testPath: Path,
columns: number,
): string => {
Expand Down Expand Up @@ -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};
Expand Down
11 changes: 7 additions & 4 deletions packages/jest-cli/src/run_jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
60 changes: 58 additions & 2 deletions packages/jest-resolve/src/__tests__/resolve.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.', () => {
Expand Down Expand Up @@ -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', {
Expand All @@ -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),
);
});
});
2 changes: 2 additions & 0 deletions packages/jest-resolve/src/default_resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ResolverOptions = {|
extensions?: Array<string>,
moduleDirectory?: Array<string>,
paths?: ?Array<Path>,
rootDir: ?Path,
|};

function defaultResolver(path: Path, options: ResolverOptions): Path {
Expand All @@ -28,6 +29,7 @@ function defaultResolver(path: Path, options: ResolverOptions): Path {
extensions: options.extensions,
moduleDirectory: options.moduleDirectory,
paths: options.paths,
rootDir: options.rootDir,
});
}

Expand Down
54 changes: 41 additions & 13 deletions packages/jest-resolve/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ResolverConfig = {|
modulePaths: Array<Path>,
platforms?: Array<string>,
resolver: ?Path,
rootDir: ?Path,
|};

type FindNodeModuleConfig = {|
Expand All @@ -38,6 +39,7 @@ type FindNodeModuleConfig = {|
moduleDirectory?: Array<string>,
paths?: Array<Path>,
resolver?: ?Path,
rootDir?: ?Path,
|};

type ModuleNameMapperConfig = {|
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -152,6 +156,7 @@ class Resolver {
moduleDirectory,
paths,
resolver: this._options.resolver,
rootDir: this._options.rootDir,
});
};

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/jest-runner/src/run_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions packages/jest-runtime/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class Runtime {
modulePaths: config.modulePaths,
platforms: config.haste.platforms,
resolver: config.resolver,
rootDir: config.rootDir,
});
}

Expand Down
1 change: 1 addition & 0 deletions test_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = {
cacheDirectory: '/test_cache_dir/',
clearMocks: false,
coveragePathIgnorePatterns: [],
cwd: '/test_root_dir/',
displayName: undefined,
globals: {},
haste: {
Expand Down
1 change: 1 addition & 0 deletions types/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export type ProjectConfig = {|
cacheDirectory: Path,
clearMocks: boolean,
coveragePathIgnorePatterns: Array<string>,
cwd: Path,
displayName: ?string,
globals: ConfigGlobals,
haste: HasteConfig,
Expand Down