Skip to content

Commit

Permalink
feat: add support for .mjs config (#9431)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Jan 21, 2020
1 parent 8236779 commit e818dca
Show file tree
Hide file tree
Showing 29 changed files with 284 additions and 130 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,7 @@
- `[jest-config]` [**BREAKING**] Set default display name color based on runner ([#8689](https://github.com/facebook/jest/pull/8689))
- `[jest-config]` Merge preset globals with project globals ([#9027](https://github.com/facebook/jest/pull/9027))
- `[jest-config]` Support `.cjs` config files ([#9291](https://github.com/facebook/jest/pull/9291))
- `[jest-config]` [**BREAKING**] Support `.mjs` config files ([#9431](https://github.com/facebook/jest/pull/9431))
- `[jest-core]` Support reporters as default exports ([#9161](https://github.com/facebook/jest/pull/9161))
- `[jest-core]` Support `--findRelatedTests` paths case insensitivity on Windows ([#8900](https://github.com/facebook/jest/issues/8900))
- `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841))
Expand Down
16 changes: 16 additions & 0 deletions babel.config.js
Expand Up @@ -19,6 +19,22 @@ module.exports = {
presets: ['@babel/preset-typescript'],
test: /\.tsx?$/,
},
// we want this file to keep `import()`, so exclude the transform for it
{
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
'@babel/preset-typescript',
[
'@babel/preset-env',
{
exclude: ['@babel/plugin-proposal-dynamic-import'],
shippedProposals: true,
targets: {node: 8},
},
],
],
test: 'packages/jest-config/src/importMjs.ts',
},
],
plugins: [
['@babel/plugin-transform-modules-commonjs', {allowTopLevelThis: true}],
Expand Down
2 changes: 1 addition & 1 deletion docs/Configuration.md
Expand Up @@ -3,7 +3,7 @@ id: configuration
title: Configuring Jest
---

Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js` file or through the `--config <path/to/file.js|cjs|json>` option. If you'd like to use your `package.json` to store Jest's config, the "jest" key should be used on the top level so Jest will know how to find your settings:
Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js` file or through the `--config <path/to/file.js|cjs|mjs|json>` option. If you'd like to use your `package.json` to store Jest's config, the `"jest"` key should be used on the top level so Jest will know how to find your settings:

```json
{
Expand Down
21 changes: 0 additions & 21 deletions e2e/__tests__/cjsConfigFile.test.ts

This file was deleted.

38 changes: 38 additions & 0 deletions e2e/__tests__/esmConfigFile.test.ts
@@ -0,0 +1,38 @@
/**
* 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.
*/

import {onNodeVersions} from '@jest/test-utils';
import {json as runWithJson} from '../runJest';

test('reads config from cjs file', () => {
const {json, exitCode} = runWithJson('esm-config/cjs', ['--show-config'], {
skipPkgJsonCheck: true,
});

expect(exitCode).toBe(0);
expect(json.configs).toHaveLength(1);
expect(json.configs[0].displayName).toEqual({
color: 'white',
name: 'Config from cjs file',
});
});

// not unflagged for other versions yet. Update this range if that changes
onNodeVersions('^13.2.0', () => {
test('reads config from mjs file', () => {
const {json, exitCode} = runWithJson('esm-config/mjs', ['--show-config'], {
skipPkgJsonCheck: true,
});

expect(exitCode).toBe(0);
expect(json.configs).toHaveLength(1);
expect(json.configs[0].displayName).toEqual({
color: 'white',
name: 'Config from mjs file',
});
});
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 10 additions & 0 deletions e2e/esm-config/mjs/__tests__/test.js
@@ -0,0 +1,10 @@
/**
* 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.
*/

test('dummy test', () => {
expect(1).toBe(1);
});
11 changes: 11 additions & 0 deletions e2e/esm-config/mjs/jest.config.mjs
@@ -0,0 +1,11 @@
/**
* 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.
*/

export default {
displayName: 'Config from mjs file',
testEnvironment: 'node',
};
3 changes: 3 additions & 0 deletions e2e/esm-config/mjs/package.json
@@ -0,0 +1,3 @@
{
"jest": {}
}
Expand Up @@ -36,6 +36,15 @@ Object {
}
`;

exports[`init has-jest-config-file-mjs ask the user whether to override config or not user answered with "Yes" 1`] = `
Object {
"initial": true,
"message": "It seems that you already have a jest configuration, do you want to override it?",
"name": "continue",
"type": "confirm",
}
`;

exports[`init project with package.json and no jest config all questions answered with answer: "No" should return the default configuration (an empty config) 1`] = `
"// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
Expand Down
@@ -0,0 +1,8 @@
/**
* 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.
*/

export default {};
@@ -0,0 +1 @@
{}
@@ -0,0 +1,7 @@
{
"name": "type_module",
"scripts": {
"test": "different-test-runner"
},
"type": "module"
}
21 changes: 20 additions & 1 deletion packages/jest-cli/src/init/__tests__/init.test.js
Expand Up @@ -9,8 +9,11 @@
import * as fs from 'fs';
import * as path from 'path';
import prompts from 'prompts';
import {constants} from 'jest-config';
import init from '../';
import {JEST_CONFIG_EXT_ORDER} from '../constants';
import {onNodeVersions} from '@jest/test-utils';

const {JEST_CONFIG_EXT_ORDER} = constants;

jest.mock('prompts');
jest.mock('../../../../jest-config/build/getCacheDirectory', () => () =>
Expand Down Expand Up @@ -52,6 +55,22 @@ describe('init', () => {

expect(evaluatedConfig).toEqual({});
});

onNodeVersions('^13.2.0', () => {
it('should generate empty config with mjs extension', async () => {
prompts.mockReturnValueOnce({});

await init(resolveFromFixture('type_module'));

const writtenJestConfigFilename = fs.writeFileSync.mock.calls[0][0];
const writtenJestConfig = fs.writeFileSync.mock.calls[0][1];

expect(writtenJestConfigFilename.endsWith('.mjs')).toBe(true);

expect(typeof writtenJestConfig).toBe('string');
expect(writtenJestConfig.split('\n')[3]).toBe('export default {');
});
});
});

describe('some questions answered with answer: "Yes"', () => {
Expand Down
17 changes: 0 additions & 17 deletions packages/jest-cli/src/init/constants.ts

This file was deleted.

7 changes: 5 additions & 2 deletions packages/jest-cli/src/init/generate_config_file.ts
Expand Up @@ -31,7 +31,10 @@ const stringifyOption = (
);
};

const generateConfigFile = (results: Record<string, unknown>): string => {
const generateConfigFile = (
results: Record<string, unknown>,
generateEsm = false,
): string => {
const {coverage, clearMocks, environment} = results;

const overrides: Record<string, any> = {};
Expand Down Expand Up @@ -75,7 +78,7 @@ const generateConfigFile = (results: Record<string, unknown>): string => {
return (
'// For a detailed explanation regarding each configuration property, visit:\n' +
'// https://jestjs.io/docs/en/configuration.html\n\n' +
'module.exports = {\n' +
(generateEsm ? 'export default {\n' : 'module.exports = {\n') +
properties.join('\n') +
'};\n'
);
Expand Down
22 changes: 14 additions & 8 deletions packages/jest-cli/src/init/index.ts
Expand Up @@ -10,18 +10,20 @@ import * as path from 'path';
import chalk = require('chalk');
import prompts = require('prompts');
import {sync as realpath} from 'realpath-native';
import {constants} from 'jest-config';
import defaultQuestions, {testScriptQuestion} from './questions';
import {MalformedPackageJsonError, NotFoundPackageJsonError} from './errors';
import {
import generateConfigFile from './generate_config_file';
import modifyPackageJson from './modify_package_json';
import {ProjectPackageJson} from './types';

const {
JEST_CONFIG_BASE_NAME,
JEST_CONFIG_EXT_CJS,
JEST_CONFIG_EXT_MJS,
JEST_CONFIG_EXT_JS,
JEST_CONFIG_EXT_ORDER,
PACKAGE_JSON,
} from './constants';
import generateConfigFile from './generate_config_file';
import modifyPackageJson from './modify_package_json';
import {ProjectPackageJson} from './types';
} = constants;

type PromptsResults = {
clearMocks: boolean;
Expand Down Expand Up @@ -65,7 +67,7 @@ export default async (rootDir: string = realpath(process.cwd())) => {
rootDir,
getConfigFilename(
projectPackageJson.type === 'module'
? JEST_CONFIG_EXT_CJS
? JEST_CONFIG_EXT_MJS
: JEST_CONFIG_EXT_JS,
),
);
Expand Down Expand Up @@ -131,7 +133,11 @@ export default async (rootDir: string = realpath(process.cwd())) => {
console.log(`✏️ Modified ${chalk.cyan(projectPackageJsonPath)}`);
}

const generatedConfig = generateConfigFile(results);
const generatedConfig = generateConfigFile(
results,
projectPackageJson.type === 'module' ||
jestConfigPath.endsWith(JEST_CONFIG_EXT_MJS),
);

fs.writeFileSync(jestConfigPath, generatedConfig);

Expand Down
2 changes: 2 additions & 0 deletions packages/jest-config/src/__tests__/Defaults.test.ts
Expand Up @@ -7,6 +7,8 @@

import {defaults} from '../index';

jest.mock('../importMjs', () => (s: string) => import(s));

test('get configuration defaults', () => {
expect(defaults).toBeDefined();
});
10 changes: 6 additions & 4 deletions packages/jest-config/src/__tests__/readConfig.test.ts
Expand Up @@ -7,16 +7,18 @@

import {readConfig} from '../index';

test('readConfig() throws when an object is passed without a file path', () => {
expect(() => {
jest.mock('../importMjs', () => (s: string) => import(s));

test('readConfig() throws when an object is passed without a file path', async () => {
await expect(
readConfig(
// @ts-ignore
null /* argv */,
{} /* packageRootOrConfig */,
false /* skipArgvConfigOption */,
null /* parentConfigPath */,
);
}).toThrowError(
),
).rejects.toThrowError(
'Jest: Cannot use configuration as an object without a file path',
);
});
10 changes: 6 additions & 4 deletions packages/jest-config/src/__tests__/readConfigs.test.ts
Expand Up @@ -7,9 +7,11 @@

import {readConfigs} from '../index';

test('readConfigs() throws when called without project paths', () => {
expect(() => {
jest.mock('../importMjs', () => (s: string) => import(s));

test('readConfigs() throws when called without project paths', async () => {
await expect(
// @ts-ignore
readConfigs(null /* argv */, [] /* projectPaths */);
}).toThrowError('jest: No configuration found for any project.');
readConfigs(null /* argv */, [] /* projectPaths */),
).rejects.toThrowError('jest: No configuration found for any project.');
});
2 changes: 2 additions & 0 deletions packages/jest-config/src/constants.ts
Expand Up @@ -13,10 +13,12 @@ export const DEFAULT_REPORTER_LABEL = 'default';
export const PACKAGE_JSON = 'package.json';
export const JEST_CONFIG_BASE_NAME = 'jest.config';
export const JEST_CONFIG_EXT_CJS = '.cjs';
export const JEST_CONFIG_EXT_MJS = '.mjs';
export const JEST_CONFIG_EXT_JS = '.js';
export const JEST_CONFIG_EXT_JSON = '.json';
export const JEST_CONFIG_EXT_ORDER = Object.freeze([
JEST_CONFIG_EXT_JS,
JEST_CONFIG_EXT_MJS,
JEST_CONFIG_EXT_CJS,
JEST_CONFIG_EXT_JSON,
]);
10 changes: 10 additions & 0 deletions packages/jest-config/src/importMjs.ts
@@ -0,0 +1,10 @@
/**
* 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.
*/

// this is in a separate file so that node 8 don't explode with a syntax error.
// Remove this file when we drop support for Node 8
export default (specifier: string) => import(specifier);

0 comments on commit e818dca

Please sign in to comment.