Skip to content

Commit c9127bf

Browse files
Support array of paths for moduleNameMapper aliases (#9465)
1 parent ffdaa75 commit c9127bf

File tree

20 files changed

+147
-38
lines changed

20 files changed

+147
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Features
44

55
- `[jest-runtime]` Override `module.createRequire` to return a Jest-compatible `require` function ([#9469](https://github.com/facebook/jest/pull/9469))
6+
- `[*]` Support array of paths for `moduleNameMapper` aliases ([#9465](https://github.com/facebook/jest/pull/9465))
67

78
### Fixes
89

docs/Configuration.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,11 @@ An array of file extensions your modules use. If you require modules without spe
458458

459459
We recommend placing the extensions most commonly used in your project on the left, so if you are using TypeScript, you may want to consider moving "ts" and/or "tsx" to the beginning of the array.
460460

461-
### `moduleNameMapper` [object\<string, string>]
461+
### `moduleNameMapper` [object\<string, string | array\<string>>]
462462

463463
Default: `null`
464464

465-
A map from regular expressions to module names that allow to stub out resources, like images or styles with a single module.
465+
A map from regular expressions to module names or to arrays of module names that allow to stub out resources, like images or styles with a single module.
466466

467467
Modules that are mapped to an alias are unmocked by default, regardless of whether automocking is enabled or not.
468468

@@ -477,12 +477,17 @@ Example:
477477
"moduleNameMapper": {
478478
"^image![a-zA-Z0-9$_-]+$": "GlobalImageStub",
479479
"^[./a-zA-Z0-9$_-]+\\.png$": "<rootDir>/RelativeImageStub.js",
480-
"module_name_(.*)": "<rootDir>/substituted_module_$1.js"
480+
"module_name_(.*)": "<rootDir>/substituted_module_$1.js",
481+
"assets/(.*)": [
482+
"<rootDir>/images/$1",
483+
"<rootDir>/photos/$1",
484+
"<rootDir>/recipes/$1"
485+
]
481486
}
482487
}
483488
```
484489

485-
The order in which the mappings are defined matters. Patterns are checked one by one until one fits. The most specific rule should be listed first.
490+
The order in which the mappings are defined matters. Patterns are checked one by one until one fits. The most specific rule should be listed first. This is true for arrays of modules names as.
486491

487492
_Note: If you provide module name without boundaries `^$` it may cause hard to spot errors. E.g. `relay` will replace all modules which contain `relay` as a substring in its name: `relay`, `react-relay` and `graphql-relay` will all be pointed to your stub._
488493

docs/TutorialReactNative.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ If you'd like to provide additional configuration for every test file, the [`set
150150

151151
### moduleNameMapper
152152

153-
The [`moduleNameMapper`](configuration.html#modulenamemapper-objectstring-string) can be used to map a module path to a different module. By default the preset maps all images to an image stub module but if a module cannot be found this configuration option can help:
153+
The [`moduleNameMapper`](configuration.html#modulenamemapper-objectstring-string--arraystring) can be used to map a module path to a different module. By default the preset maps all images to an image stub module but if a module cannot be found this configuration option can help:
154154

155155
```json
156156
"moduleNameMapper": {

e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,41 @@ PASS __tests__/index.js
55
✓ moduleNameMapping correct configuration
66
`;
77

8+
exports[`moduleNameMapper wrong array configuration 1`] = `
9+
FAIL __tests__/index.js
10+
● Test suite failed to run
11+
12+
Configuration error:
13+
14+
Could not locate module ./style.css mapped as:
15+
[
16+
"no-such-module",
17+
"no-such-module-2"
18+
].
19+
20+
Please check your configuration for these entries:
21+
{
22+
"moduleNameMapper": {
23+
"/\\.(css|less)$/": "[
24+
"no-such-module",
25+
"no-such-module-2"
26+
]"
27+
},
28+
"resolver": undefined
29+
}
30+
31+
8 | 'use strict';
32+
9 |
33+
> 10 | require('./style.css');
34+
| ^
35+
11 |
36+
12 | module.exports = () => 'test';
37+
13 |
38+
39+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:507:17)
40+
at Object.require (index.js:10:1)
41+
`;
42+
843
exports[`moduleNameMapper wrong configuration 1`] = `
944
FAIL __tests__/index.js
1045
● Test suite failed to run
@@ -30,6 +65,6 @@ FAIL __tests__/index.js
3065
12 | module.exports = () => 'test';
3166
13 |
3267
33-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:487:17)
68+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:507:17)
3469
at Object.require (index.js:10:1)
3570
`;

e2e/__tests__/moduleNameMapper.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ test('moduleNameMapper wrong configuration', () => {
1717
expect(wrap(rest)).toMatchSnapshot();
1818
});
1919

20+
test('moduleNameMapper wrong array configuration', () => {
21+
const {stderr, exitCode} = runJest('module-name-mapper-wrong-array-config');
22+
const {rest} = extractSummary(stderr);
23+
24+
expect(exitCode).toBe(1);
25+
expect(wrap(rest)).toMatchSnapshot();
26+
});
27+
2028
test('moduleNameMapper correct configuration', () => {
2129
const {stderr, exitCode} = runJest('module-name-mapper-correct-config', [], {
2230
stripAnsi: true,

e2e/module-name-mapper-correct-config/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
'use strict';
99

1010
require('./style.css');
11+
require('./style.sass');
1112

1213
module.exports = () => 'test';
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"jest": {
33
"moduleNameMapper": {
4-
"\\.(css|less)$": "./__mocks__/styleMock.js"
4+
"\\.(css|less)$": "./__mocks__/styleMock.js",
5+
"\\.(sass)$": ["./__mocks__/nonExistentMock.js", "./__mocks__/styleMock.js"]
56
}
67
}
78
}

e2e/module-name-mapper-correct-config/style.sass

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const importedFn = require('../');
11+
12+
test('moduleNameMapping wrong configuration', () => {
13+
expect(importedFn).toBeDefined();
14+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
require('./style.css');
11+
12+
module.exports = () => 'test';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"jest": {
3+
"moduleNameMapper": {
4+
"\\.(css|less)$": ["no-such-module", "no-such-module-2"]
5+
}
6+
}
7+
}

e2e/module-name-mapper-wrong-array-config/style.css

Whitespace-only changes.

packages/jest-cli/src/cli/args.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,8 @@ export const options = {
377377
moduleNameMapper: {
378378
description:
379379
'A JSON string with a map from regular expressions to ' +
380-
'module names that allow to stub out resources, like images or ' +
381-
'styles with a single module',
380+
'module names or to arrays of module names that allow to stub ' +
381+
'out resources, like images or styles with a single module',
382382
type: 'string',
383383
},
384384
modulePathIgnorePatterns: {

packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ module.exports = {
126126
// \\"node\\"
127127
// ],
128128
129-
// A map from regular expressions to module names that allow to stub out resources with a single module
129+
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
130130
// moduleNameMapper: {},
131131
132132
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader

packages/jest-config/src/Descriptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const descriptions: {[key in keyof Config.InitialOptions]: string} = {
4343
"An array of directory names to be searched recursively up from the requiring module's location",
4444
moduleFileExtensions: 'An array of file extensions your modules use',
4545
moduleNameMapper:
46-
'A map from regular expressions to module names that allow to stub out resources with a single module',
46+
'A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module',
4747
modulePathIgnorePatterns:
4848
"An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader",
4949
notify: 'Activates notifications for test results',

packages/jest-config/src/__tests__/setFromArgv.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ test('maps regular values to themselves', () => {
4545
test('works with string objects', () => {
4646
const options = {} as Config.InitialOptions;
4747
const argv = {
48-
moduleNameMapper: '{"types/(.*)": "<rootDir>/src/types/$1"}',
48+
moduleNameMapper:
49+
'{"types/(.*)": "<rootDir>/src/types/$1", "types2/(.*)": ["<rootDir>/src/types2/$1", "<rootDir>/src/types3/$1"]}',
4950
transform: '{"*.js": "<rootDir>/transformer"}',
5051
} as Config.Argv;
5152
expect(setFromArgv(options, argv)).toMatchObject({
5253
moduleNameMapper: {
5354
'types/(.*)': '<rootDir>/src/types/$1',
55+
'types2/(.*)': ['<rootDir>/src/types2/$1', '<rootDir>/src/types3/$1'],
5456
},
5557
transform: {
5658
'*.js': '<rootDir>/transformer',

packages/jest-resolve/src/index.ts

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -377,28 +377,42 @@ class Resolver {
377377
// Note: once a moduleNameMapper matches the name, it must result
378378
// in a module, or else an error is thrown.
379379
const matches = moduleName.match(regex);
380-
const updatedName = matches
381-
? mappedModuleName.replace(
382-
/\$([0-9]+)/g,
383-
(_, index) => matches[parseInt(index, 10)],
384-
)
385-
: mappedModuleName;
386-
387-
const module =
388-
this.getModule(updatedName) ||
389-
Resolver.findNodeModule(updatedName, {
390-
basedir: dirname,
391-
browser: this._options.browser,
392-
extensions,
393-
moduleDirectory,
394-
paths,
395-
resolver,
396-
rootDir: this._options.rootDir,
397-
});
380+
const mapModuleName = matches
381+
? (moduleName: string) =>
382+
moduleName.replace(
383+
/\$([0-9]+)/g,
384+
(_, index) => matches[parseInt(index, 10)],
385+
)
386+
: (moduleName: string) => moduleName;
387+
388+
const possibleModuleNames = Array.isArray(mappedModuleName)
389+
? mappedModuleName
390+
: [mappedModuleName];
391+
let module: string | null = null;
392+
for (const possibleModuleName of possibleModuleNames) {
393+
const updatedName = mapModuleName(possibleModuleName);
394+
395+
module =
396+
this.getModule(updatedName) ||
397+
Resolver.findNodeModule(updatedName, {
398+
basedir: dirname,
399+
browser: this._options.browser,
400+
extensions,
401+
moduleDirectory,
402+
paths,
403+
resolver,
404+
rootDir: this._options.rootDir,
405+
});
406+
407+
if (module) {
408+
break;
409+
}
410+
}
411+
398412
if (!module) {
399413
throw createNoMappedModuleFoundError(
400414
moduleName,
401-
updatedName,
415+
mapModuleName,
402416
mappedModuleName,
403417
regex,
404418
resolver,
@@ -414,21 +428,29 @@ class Resolver {
414428

415429
const createNoMappedModuleFoundError = (
416430
moduleName: string,
417-
updatedName: string,
418-
mappedModuleName: string,
431+
mapModuleName: (moduleName: string) => string,
432+
mappedModuleName: string | Array<string>,
419433
regex: RegExp,
420434
resolver?: Function | string | null,
421435
) => {
436+
const mappedAs = Array.isArray(mappedModuleName)
437+
? JSON.stringify(mappedModuleName.map(mapModuleName), null, 2)
438+
: mappedModuleName;
439+
const original = Array.isArray(mappedModuleName)
440+
? JSON.stringify(mappedModuleName, null, 6) // using 6 because of misalignment when nested below
441+
.slice(0, -1) + ' ]' /// align last bracket correctly as well
442+
: mappedModuleName;
443+
422444
const error = new Error(
423445
chalk.red(`${chalk.bold('Configuration error')}:
424446
425447
Could not locate module ${chalk.bold(moduleName)} mapped as:
426-
${chalk.bold(updatedName)}.
448+
${chalk.bold(mappedAs)}.
427449
428450
Please check your configuration for these entries:
429451
{
430452
"moduleNameMapper": {
431-
"${regex.toString()}": "${chalk.bold(mappedModuleName)}"
453+
"${regex.toString()}": "${chalk.bold(original)}"
432454
},
433455
"resolver": ${chalk.bold(String(resolver))}
434456
}`),

packages/jest-resolve/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ export type ResolverConfig = {
2222

2323
type ModuleNameMapperConfig = {
2424
regex: RegExp;
25-
moduleName: string;
25+
moduleName: string | Array<string>;
2626
};

packages/jest-types/src/Config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export type DefaultOptions = {
5050
maxWorkers: number | string;
5151
moduleDirectories: Array<string>;
5252
moduleFileExtensions: Array<string>;
53-
moduleNameMapper: Record<string, string>;
53+
moduleNameMapper: Record<string, string | Array<string>>;
5454
modulePathIgnorePatterns: Array<string>;
5555
noStackTrace: boolean;
5656
notify: boolean;
@@ -143,7 +143,7 @@ export type InitialOptions = Partial<{
143143
moduleFileExtensions: Array<string>;
144144
moduleLoader: Path;
145145
moduleNameMapper: {
146-
[key: string]: string;
146+
[key: string]: string | Array<string>;
147147
};
148148
modulePathIgnorePatterns: Array<string>;
149149
modulePaths: Array<string>;

packages/jest-validate/src/__tests__/fixtures/jestConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const validConfig = {
9191
moduleLoader: '<rootDir>',
9292
moduleNameMapper: {
9393
'^React$': '<rootDir>/node_modules/react',
94+
'^Vue$': ['<rootDir>/node_modules/vue', '<rootDir>/node_modules/vue3'],
9495
},
9596
modulePathIgnorePatterns: ['<rootDir>/build/'],
9697
modulePaths: ['/shared/vendor/modules'],

0 commit comments

Comments
 (0)