Skip to content

Commit

Permalink
feat(config): adds a helper to build moduleNameMapper from paths
Browse files Browse the repository at this point in the history
Closes #364
  • Loading branch information
huafu committed Sep 1, 2018
1 parent 1eaa6a6 commit 7b8598e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 1 deletion.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,19 @@ module.exports = {
};
```

TS Jest provides a helper to automatically create this map from the `paths` compiler option of your TS config:

```js
// jest.config.js
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig'); // replace with the path to your tsconfig.json file
module.exports = {
// [...]
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths /*, { prefix: '<rootDir>/' } */ )
};
```

### Using `babel-jest`
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:

Expand Down
71 changes: 71 additions & 0 deletions src/config/paths-to-module-name-mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { pathsToModuleNameMapper } from './paths-to-module-name-mapper'
import { logTargetMock } from '../__helpers__/mocks'

const tsconfigMap = {
log: ['src/util/log'],
server: ['src/server'],
'util/*': ['src/util/*'],
'api/*': ['src/api/*'],
'test/*': ['test/*'],
'mocks/*': ['test/mocks/*'],
'test/*/mock': ['test/mocks/*'],
}

describe('pathsToModuleNameMapper', () => {
it('should convert tsconfig mapping', () => {
expect(pathsToModuleNameMapper(tsconfigMap)).toMatchInlineSnapshot(`
Object {
"^api/(.*)$": "src/api/$1",
"^log$": "src/util/log",
"^mocks/(.*)$": "test/mocks/$1",
"^server$": "src/server",
"^test/(.*)$": "test/$1",
"^test/(.*)/mock$": "test/mocks/$1",
"^util/(.*)$": "src/util/$1",
}
`)
})

it('should use the given prefix', () => {
expect(pathsToModuleNameMapper(tsconfigMap, { prefix: '<rootDir>/' }))
.toMatchInlineSnapshot(`
Object {
"^api/(.*)$": "<rootDir>/src/api/$1",
"^log$": "<rootDir>/src/util/log",
"^mocks/(.*)$": "<rootDir>/test/mocks/$1",
"^server$": "<rootDir>/src/server",
"^test/(.*)$": "<rootDir>/test/$1",
"^test/(.*)/mock$": "<rootDir>/test/mocks/$1",
"^util/(.*)$": "<rootDir>/src/util/$1",
}
`)
})

it('should warn about mapping it cannot handle', () => {
const log = logTargetMock()
log.clear()
expect(
pathsToModuleNameMapper({
kept: ['src/kept'],
'no-target': [],
'too-many-target': ['one', 'two'],
'too/*/many/*/stars': ['to/*/many/*/stars'],
}),
).toMatchInlineSnapshot(`
Object {
"^kept$": "src/kept",
"^too\\\\-many\\\\-target$": "one",
}
`)
expect(log.lines.warn).toMatchInlineSnapshot(`
Array [
"[level:40] Not mapping \\"no-target\\" because it has no target.
",
"[level:40] Mapping only to first target of \\"too-many-target\\" becuase it has more than one (2).
",
"[level:40] Not mapping \\"too/*/many/*/stars\\" because it has more than one star (\`*\`).
",
]
`)
})
})
55 changes: 55 additions & 0 deletions src/config/paths-to-module-name-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { CompilerOptions } from 'typescript'
import { rootLogger } from '../util/logger'
import { interpolate, Errors } from '../util/messages'
import { LogContexts } from 'bs-logger'

type TsPathMapping = Exclude<CompilerOptions['paths'], undefined>
type JestPathMapping = jest.InitialOptions['moduleNameMapper']

// we don't need to escape all chars, so commented out is the real one
// const escapeRegex = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
const escapeRegex = (str: string) => str.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&')

const logger = rootLogger.child({ [LogContexts.namespace]: 'path-mapper' })

export const pathsToModuleNameMapper = (
mapping: TsPathMapping,
{ prefix = '' }: { prefix?: string } = {},
): JestPathMapping => {
const jestMap: JestPathMapping = {}
for (const fromPath of Object.keys(mapping)) {
let pattern: string
const toPaths = mapping[fromPath]
// check that we have only one target path
if (toPaths.length === 0) {
logger.warn(
interpolate(Errors.NotMappingPathWithEmptyMap, { path: fromPath }),
)
continue
} else if (toPaths.length > 1) {
logger.warn(
interpolate(Errors.MappingOnlyFirstTargetOfPath, {
path: fromPath,
count: toPaths.length,
}),
)
}
const target = toPaths[0]

// split with '*'
const segments = fromPath.split(/\*/g)
if (segments.length === 1) {
pattern = `^${escapeRegex(fromPath)}$`
jestMap[pattern] = `${prefix}${target}`
} else if (segments.length === 2) {
pattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex(segments[1])}$`
jestMap[pattern] = `${prefix}${target.replace(/\*/g, '$1')}`
} else {
logger.warn(
interpolate(Errors.NotMappingMultiStarPath, { path: fromPath }),
)
continue
}
}
return jestMap
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TsJestTransformer } from './ts-jest-transformer'
import { createJestPreset } from './config/create-jest-preset'
import { TsJestGlobalOptions } from './types'
import { VersionCheckers } from './util/version-checkers'
import { pathsToModuleNameMapper } from './config/paths-to-module-name-mapper'

// tslint:disable-next-line:no-var-requires
const version: string = require('../package.json').version
Expand Down Expand Up @@ -39,6 +40,7 @@ export {
// extra ==================
createJestPreset,
jestPreset,
pathsToModuleNameMapper,
// tests ==================
__singleton,
__resetModule,
Expand Down
1 change: 0 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import _ts, { CompilerOptions } from 'typescript'
import * as _babel from 'babel__core'
import { Writable } from 'stream'

export type TBabelCore = typeof _babel
export type TTypeScript = typeof _ts
Expand Down
3 changes: 3 additions & 0 deletions src/util/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export enum Errors {
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.",
MissingDependency = "Module {{module}} is not installed. If you're experiencing issues, consider installing a supported version ({{expectedVersion}}).",
UnableToCompileTypeScript = 'Unable to compile TypeScript ({{help}}):\n{{diagnostics}}',
NotMappingMultiStarPath = 'Not mapping "{{path}}" because it has more than one star (`*`).',
NotMappingPathWithEmptyMap = 'Not mapping "{{path}}" because it has no target.',
MappingOnlyFirstTargetOfPath = 'Mapping only to first target of "{{path}}" becuase it has more than one ({{count}}).',
}

export enum Helps {
Expand Down

0 comments on commit 7b8598e

Please sign in to comment.