Skip to content

Commit

Permalink
Transformer configs (#7288)
Browse files Browse the repository at this point in the history
  • Loading branch information
eightypop authored and SimenB committed Jun 26, 2019
1 parent b644d85 commit 47da9cf
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@
- `[*]` Manage the global timeout with `--testTimeout` command line argument. ([#8456](https://github.com/facebook/jest/pull/8456))
- `[pretty-format]` Render custom displayName of memoized components
- `[jest-validate]` Allow `maxWorkers` as part of the `jest.config.js` ([#8565](https://github.com/facebook/jest/pull/8565))
- `[jest-runtime]` Allow passing configuration objects to transformers ([#7288](https://github.com/facebook/jest/pull/7288))

### Fixes

Expand Down
4 changes: 3 additions & 1 deletion docs/Configuration.md
Expand Up @@ -1072,14 +1072,16 @@ Default: `real`

Setting this value to `fake` allows the use of fake timers for functions such as `setTimeout`. Fake timers are useful when a piece of code sets a long timeout that we don't want to wait for in a test.

### `transform` [object<string, string>]
### `transform` [object<string, pathToTransformer | [pathToTransformer, object]>]

Default: `undefined`

A map from regular expressions to paths to transformers. A transformer is a module that provides a synchronous function for transforming source files. For example, if you wanted to be able to use a new language feature in your modules or tests that isn't yet supported by node, you might plug in one of many compilers that compile a future version of JavaScript to a current one. Example: see the [examples/typescript](https://github.com/facebook/jest/blob/master/examples/typescript/package.json#L16) example or the [webpack tutorial](Webpack.md).

Examples of such compilers include [Babel](https://babeljs.io/), [TypeScript](http://www.typescriptlang.org/) and [async-to-gen](http://github.com/leebyron/async-to-gen#jest).

You can pass configuration to a transformer like `{filePattern: ['path-to-transformer', {options}]}` For example, to configure babel-jest for non-default behavior, `{"\\.js$": ['babel-jest', {rootMode: "upward"}]}`

_Note: a transformer is only run once per file unless the file has changed. During development of a transformer it can be useful to run Jest with `--no-cache` to frequently [delete Jest's cache](Troubleshooting.md#caching-issues)._

_Note: if you are using the `babel-jest` transformer and want to use an additional code preprocessor, keep in mind that when "transform" is overwritten in any way the `babel-jest` is not loaded automatically anymore. If you want to use it to compile JavaScript code it has to be explicitly defined. See [babel-jest plugin](https://github.com/facebook/jest/tree/master/packages/babel-jest#setup)_
Expand Down
9 changes: 9 additions & 0 deletions e2e/__tests__/__snapshots__/transform.test.ts.snap
Expand Up @@ -35,3 +35,12 @@ All files | 83.33 | 100 | 50 | 80 | |
------------|----------|----------|----------|----------|-------------------|
`;

exports[`transformer-config instruments only specific files and collects coverage 1`] = `
"------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 83.33 | 100 | 50 | 80 | |
Covered.js | 83.33 | 100 | 50 | 80 | 13 |
------------|----------|----------|----------|----------|-------------------|"
`;
26 changes: 26 additions & 0 deletions e2e/__tests__/transform.test.ts
Expand Up @@ -159,3 +159,29 @@ describe('ecmascript-modules-support', () => {
expect(json.numTotalTests).toBeGreaterThanOrEqual(1);
});
});

describe('transformer-config', () => {
const dir = path.resolve(__dirname, '..', 'transform/transformer-config');

beforeEach(() => {
run('yarn', dir);
});

it('runs transpiled code', () => {
// --no-cache because babel can cache stuff and result in false green
const {json} = runWithJson(dir, ['--no-cache']);
expect(json.success).toBe(true);
expect(json.numTotalTests).toBeGreaterThanOrEqual(1);
});

it('instruments only specific files and collects coverage', () => {
const {stdout} = runJest(dir, ['--coverage', '--no-cache'], {
stripAnsi: true,
});
expect(stdout).toMatch('Covered.js');
expect(stdout).not.toMatch('NotCovered.js');
expect(stdout).not.toMatch('ExcludedFromCoverage.js');
// coverage result should not change
expect(stdout).toMatchSnapshot();
});
});
1 change: 1 addition & 0 deletions e2e/transform/transformer-config/.babelrc
@@ -0,0 +1 @@
{}
12 changes: 12 additions & 0 deletions e2e/transform/transformer-config/NotCovered.js
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const thisFunctionIsNeverInstrumented = () => null;

module.exports = {
thisFunctionIsNeverInstrumented,
};
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

require('../this-directory-is-covered/ExcludedFromCoverage');

it('strips flowtypes using babel-jest and config passed to transformer', () => {
const a: string = 'a';
expect(a).toBe('a');
});
25 changes: 25 additions & 0 deletions e2e/transform/transformer-config/package.json
@@ -0,0 +1,25 @@
{
"dependencies": {
"@babel/preset-flow": "7.0.0"
},
"jest": {
"collectCoverageOnlyFrom": {
"<rootDir>/this-directory-is-covered/Covered.js": true,
"<rootDir>/this-directory-is-covered/ExcludedFromCoverage.js": true
},
"coveragePathIgnorePatterns": [
"ExcludedFromCoverage"
],
"testEnvironment": "node",
"transform": {
"\\.js$": [
"babel-jest",
{
"presets": [
"@babel/preset-flow"
]
}
]
}
}
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const thisFunctionIsCovered = (): null => null;

thisFunctionIsCovered();

const thisFunctionIsNotCovered = (): void => {
throw new Error('Never Called');
};

module.exports = {
thisFunctionIsCovered,
thisFunctionIsNotCovered,
};
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

require('./Covered');
require('../NotCovered');

const thisFunctionIsNeverInstrumented = () => null;

module.exports = {
thisFunctionIsNeverInstrumented,
};
34 changes: 34 additions & 0 deletions e2e/transform/transformer-config/yarn.lock
@@ -0,0 +1,34 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


babel-plugin-syntax-flow@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=

babel-plugin-transform-flow-strip-types@6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.8.0.tgz#2351d85e3a52152e1a55d3f08ae635e21ece17a0"
integrity sha1-I1HYXjpSFS4aVdPwiuY14h7OF6A=
dependencies:
babel-plugin-syntax-flow "^6.8.0"
babel-runtime "^6.0.0"

babel-runtime@^6.0.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
integrity sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"

core-js@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
integrity sha1-TekR5mew6ukSTjQlS1OupvxhjT4=

regenerator-runtime@^0.10.0:
version "0.10.4"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.4.tgz#74cb6598d3ba2eb18694e968a40e2b3b4df9cf93"
integrity sha1-dMtlmNO6LrGGlOlopA4rO035z5M=
18 changes: 18 additions & 0 deletions packages/jest-config/src/__tests__/normalize.test.js
Expand Up @@ -331,6 +331,24 @@ describe('transform', () => {
['abs-path', '/qux/quux'],
]);
});
it("pulls in config if it's passed as an array", () => {
const {options} = normalize(
{
rootDir: '/root/',
transform: {
[DEFAULT_CSS_PATTERN]: '<rootDir>/node_modules/jest-regex-util',
[DEFAULT_JS_PATTERN]: ['babel-jest', {rootMode: 'upward'}],
'abs-path': '/qux/quux',
},
},
{},
);
expect(options.transform).toEqual([
[DEFAULT_CSS_PATTERN, '/root/node_modules/jest-regex-util'],
[DEFAULT_JS_PATTERN, require.resolve('babel-jest'), {rootMode: 'upward'}],
['abs-path', '/qux/quux'],
]);
});
});

describe('haste', () => {
Expand Down
95 changes: 55 additions & 40 deletions packages/jest-config/src/normalize.ts
Expand Up @@ -46,19 +46,29 @@ type AllOptions = Config.ProjectConfig & Config.GlobalConfig;
const createConfigError = (message: string) =>
new ValidationError(ERROR, message, DOCUMENTATION_NOTE);

const mergeOptionWithPreset = (
// TS 3.5 forces us to split these into 2
const mergeModuleNameMapperWithPreset = (
options: Config.InitialOptions,
preset: Config.InitialOptions,
) => {
if (options['moduleNameMapper'] && preset['moduleNameMapper']) {
options['moduleNameMapper'] = {
...options['moduleNameMapper'],
...preset['moduleNameMapper'],
...options['moduleNameMapper'],
};
}
};

const mergeTransformWithPreset = (
options: Config.InitialOptions,
preset: Config.InitialOptions,
optionName: keyof Pick<
Config.InitialOptions,
'moduleNameMapper' | 'transform'
>,
) => {
if (options[optionName] && preset[optionName]) {
options[optionName] = {
...options[optionName],
...preset[optionName],
...options[optionName],
if (options['transform'] && preset['transform']) {
options['transform'] = {
...options['transform'],
...preset['transform'],
...options['transform'],
};
}
};
Expand Down Expand Up @@ -121,8 +131,8 @@ const setupPreset = (
options.modulePathIgnorePatterns,
);
}
mergeOptionWithPreset(options, preset, 'moduleNameMapper');
mergeOptionWithPreset(options, preset, 'transform');
mergeModuleNameMapperWithPreset(options, preset);
mergeTransformWithPreset(options, preset);

return {...preset, ...options};
};
Expand All @@ -140,27 +150,26 @@ const setupBabelJest = (options: Config.InitialOptions) => {
return regex.test('a.ts') || regex.test('a.tsx');
});

if (customJSPattern) {
const customJSTransformer = transform[customJSPattern];

if (customJSTransformer === 'babel-jest') {
babelJest = require.resolve('babel-jest');
transform[customJSPattern] = babelJest;
} else if (customJSTransformer.includes('babel-jest')) {
babelJest = customJSTransformer;
}
}

if (customTSPattern) {
const customTSTransformer = transform[customTSPattern];

if (customTSTransformer === 'babel-jest') {
babelJest = require.resolve('babel-jest');
transform[customTSPattern] = babelJest;
} else if (customTSTransformer.includes('babel-jest')) {
babelJest = customTSTransformer;
[customJSPattern, customTSPattern].forEach(pattern => {
if (pattern) {
const customTransformer = transform[pattern];
if (Array.isArray(customTransformer)) {
if (customTransformer[0] === 'babel-jest') {
babelJest = require.resolve('babel-jest');
customTransformer[0] = babelJest;
} else if (customTransformer[0].includes('babel-jest')) {
babelJest = customTransformer[0];
}
} else {
if (customTransformer === 'babel-jest') {
babelJest = require.resolve('babel-jest');
transform[pattern] = babelJest;
} else if (customTransformer.includes('babel-jest')) {
babelJest = customTransformer;
}
}
}
}
});
} else {
babelJest = require.resolve('babel-jest');
options.transform = {
Expand Down Expand Up @@ -620,14 +629,20 @@ export default function normalize(
const transform = oldOptions[key];
value =
transform &&
Object.keys(transform).map(regex => [
regex,
resolve(newOptions.resolver, {
filePath: transform[regex],
key,
rootDir: options.rootDir,
}),
]);
Object.keys(transform).map(regex => {
const transformElement = transform[regex];
return [
regex,
resolve(newOptions.resolver, {
filePath: Array.isArray(transformElement)
? transformElement[0]
: transformElement,
key,
rootDir: options.rootDir,
}),
...(Array.isArray(transformElement) ? [transformElement[1]] : []),
];
});
break;
case 'coveragePathIgnorePatterns':
case 'modulePathIgnorePatterns':
Expand Down

0 comments on commit 47da9cf

Please sign in to comment.