Skip to content

Commit 7b8598e

Browse files
committed
feat(config): adds a helper to build moduleNameMapper from paths
Closes #364
1 parent 1eaa6a6 commit 7b8598e

6 files changed

Lines changed: 144 additions & 1 deletion

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,19 @@ module.exports = {
204204
};
205205
```
206206

207+
TS Jest provides a helper to automatically create this map from the `paths` compiler option of your TS config:
208+
209+
```js
210+
// jest.config.js
211+
const { pathsToModuleNameMapper } = require('ts-jest');
212+
const { compilerOptions } = require('./tsconfig'); // replace with the path to your tsconfig.json file
213+
214+
module.exports = {
215+
// [...]
216+
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths /*, { prefix: '<rootDir>/' } */ )
217+
};
218+
```
219+
207220
### Using `babel-jest`
208221
By default ts-jest does not rely on babel-jest. But you may want to use some babel plugins and stll be able to write TypeScript. This can be achieved using the `babelConfig` config key. It can be:
209222

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { pathsToModuleNameMapper } from './paths-to-module-name-mapper'
2+
import { logTargetMock } from '../__helpers__/mocks'
3+
4+
const tsconfigMap = {
5+
log: ['src/util/log'],
6+
server: ['src/server'],
7+
'util/*': ['src/util/*'],
8+
'api/*': ['src/api/*'],
9+
'test/*': ['test/*'],
10+
'mocks/*': ['test/mocks/*'],
11+
'test/*/mock': ['test/mocks/*'],
12+
}
13+
14+
describe('pathsToModuleNameMapper', () => {
15+
it('should convert tsconfig mapping', () => {
16+
expect(pathsToModuleNameMapper(tsconfigMap)).toMatchInlineSnapshot(`
17+
Object {
18+
"^api/(.*)$": "src/api/$1",
19+
"^log$": "src/util/log",
20+
"^mocks/(.*)$": "test/mocks/$1",
21+
"^server$": "src/server",
22+
"^test/(.*)$": "test/$1",
23+
"^test/(.*)/mock$": "test/mocks/$1",
24+
"^util/(.*)$": "src/util/$1",
25+
}
26+
`)
27+
})
28+
29+
it('should use the given prefix', () => {
30+
expect(pathsToModuleNameMapper(tsconfigMap, { prefix: '<rootDir>/' }))
31+
.toMatchInlineSnapshot(`
32+
Object {
33+
"^api/(.*)$": "<rootDir>/src/api/$1",
34+
"^log$": "<rootDir>/src/util/log",
35+
"^mocks/(.*)$": "<rootDir>/test/mocks/$1",
36+
"^server$": "<rootDir>/src/server",
37+
"^test/(.*)$": "<rootDir>/test/$1",
38+
"^test/(.*)/mock$": "<rootDir>/test/mocks/$1",
39+
"^util/(.*)$": "<rootDir>/src/util/$1",
40+
}
41+
`)
42+
})
43+
44+
it('should warn about mapping it cannot handle', () => {
45+
const log = logTargetMock()
46+
log.clear()
47+
expect(
48+
pathsToModuleNameMapper({
49+
kept: ['src/kept'],
50+
'no-target': [],
51+
'too-many-target': ['one', 'two'],
52+
'too/*/many/*/stars': ['to/*/many/*/stars'],
53+
}),
54+
).toMatchInlineSnapshot(`
55+
Object {
56+
"^kept$": "src/kept",
57+
"^too\\\\-many\\\\-target$": "one",
58+
}
59+
`)
60+
expect(log.lines.warn).toMatchInlineSnapshot(`
61+
Array [
62+
"[level:40] Not mapping \\"no-target\\" because it has no target.
63+
",
64+
"[level:40] Mapping only to first target of \\"too-many-target\\" becuase it has more than one (2).
65+
",
66+
"[level:40] Not mapping \\"too/*/many/*/stars\\" because it has more than one star (\`*\`).
67+
",
68+
]
69+
`)
70+
})
71+
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { CompilerOptions } from 'typescript'
2+
import { rootLogger } from '../util/logger'
3+
import { interpolate, Errors } from '../util/messages'
4+
import { LogContexts } from 'bs-logger'
5+
6+
type TsPathMapping = Exclude<CompilerOptions['paths'], undefined>
7+
type JestPathMapping = jest.InitialOptions['moduleNameMapper']
8+
9+
// we don't need to escape all chars, so commented out is the real one
10+
// const escapeRegex = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
11+
const escapeRegex = (str: string) => str.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&')
12+
13+
const logger = rootLogger.child({ [LogContexts.namespace]: 'path-mapper' })
14+
15+
export const pathsToModuleNameMapper = (
16+
mapping: TsPathMapping,
17+
{ prefix = '' }: { prefix?: string } = {},
18+
): JestPathMapping => {
19+
const jestMap: JestPathMapping = {}
20+
for (const fromPath of Object.keys(mapping)) {
21+
let pattern: string
22+
const toPaths = mapping[fromPath]
23+
// check that we have only one target path
24+
if (toPaths.length === 0) {
25+
logger.warn(
26+
interpolate(Errors.NotMappingPathWithEmptyMap, { path: fromPath }),
27+
)
28+
continue
29+
} else if (toPaths.length > 1) {
30+
logger.warn(
31+
interpolate(Errors.MappingOnlyFirstTargetOfPath, {
32+
path: fromPath,
33+
count: toPaths.length,
34+
}),
35+
)
36+
}
37+
const target = toPaths[0]
38+
39+
// split with '*'
40+
const segments = fromPath.split(/\*/g)
41+
if (segments.length === 1) {
42+
pattern = `^${escapeRegex(fromPath)}$`
43+
jestMap[pattern] = `${prefix}${target}`
44+
} else if (segments.length === 2) {
45+
pattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex(segments[1])}$`
46+
jestMap[pattern] = `${prefix}${target.replace(/\*/g, '$1')}`
47+
} else {
48+
logger.warn(
49+
interpolate(Errors.NotMappingMultiStarPath, { path: fromPath }),
50+
)
51+
continue
52+
}
53+
}
54+
return jestMap
55+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TsJestTransformer } from './ts-jest-transformer'
22
import { createJestPreset } from './config/create-jest-preset'
33
import { TsJestGlobalOptions } from './types'
44
import { VersionCheckers } from './util/version-checkers'
5+
import { pathsToModuleNameMapper } from './config/paths-to-module-name-mapper'
56

67
// tslint:disable-next-line:no-var-requires
78
const version: string = require('../package.json').version
@@ -39,6 +40,7 @@ export {
3940
// extra ==================
4041
createJestPreset,
4142
jestPreset,
43+
pathsToModuleNameMapper,
4244
// tests ==================
4345
__singleton,
4446
__resetModule,

src/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import _ts, { CompilerOptions } from 'typescript'
22
import * as _babel from 'babel__core'
3-
import { Writable } from 'stream'
43

54
export type TBabelCore = typeof _babel
65
export type TTypeScript = typeof _ts

src/util/messages.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export enum Errors {
88
UntestedDependencyVersion = "Version {{actualVersion}} of {{module}} installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version ({{expectedVersion}}). Please do not report issues in ts-jest if you are using unsupported versions.",
99
MissingDependency = "Module {{module}} is not installed. If you're experiencing issues, consider installing a supported version ({{expectedVersion}}).",
1010
UnableToCompileTypeScript = 'Unable to compile TypeScript ({{help}}):\n{{diagnostics}}',
11+
NotMappingMultiStarPath = 'Not mapping "{{path}}" because it has more than one star (`*`).',
12+
NotMappingPathWithEmptyMap = 'Not mapping "{{path}}" because it has no target.',
13+
MappingOnlyFirstTargetOfPath = 'Mapping only to first target of "{{path}}" becuase it has more than one ({{count}}).',
1114
}
1215

1316
export enum Helps {

0 commit comments

Comments
 (0)