Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for .mjs config #9431

Merged
merged 9 commits into from Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);