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

Support '.js', '.cjs', '.json' configs #9291

Merged
merged 8 commits into from Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -11,6 +11,7 @@
- `[jest-config]` Throw the full error message and stack when a Jest preset is missing a dependency ([#8924](https://github.com/facebook/jest/pull/8924))
- `[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-core]` Support reporters as default exports ([#9161](https://github.com/facebook/jest/pull/9161))
- `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841))
- `[jest-diff]` [**BREAKING**] Export as ECMAScript module ([#8873](https://github.com/facebook/jest/pull/8873))
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/js|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|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
Expand Up @@ -9,7 +9,25 @@ Object {
}
`;

exports[`init has-jest-config-file ask the user whether to override config or not user answered with "Yes" 1`] = `
exports[`init has-jest-config-file-cjs 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 has-jest-config-file-js 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 has-jest-config-file-json 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?",
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.
*/

module.exports = {};
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
35 changes: 19 additions & 16 deletions packages/jest-cli/src/init/__tests__/init.test.js
Expand Up @@ -10,6 +10,7 @@ import * as fs from 'fs';
import * as path from 'path';
import prompts from 'prompts';
import init from '../';
import {JEST_CONFIG_EXT_ORDER} from '../constants';

jest.mock('prompts');
jest.mock('../../../../jest-config/build/getCacheDirectory', () => () =>
Expand Down Expand Up @@ -125,29 +126,31 @@ describe('init', () => {
});
});

describe('has-jest-config-file', () => {
describe('ask the user whether to override config or not', () => {
it('user answered with "Yes"', async () => {
prompts.mockReturnValueOnce({continue: true}).mockReturnValueOnce({});
JEST_CONFIG_EXT_ORDER.map(e => e.substring(1)).forEach(extension =>
describe(`has-jest-config-file-${extension}`, () => {
SimenB marked this conversation as resolved.
Show resolved Hide resolved
describe('ask the user whether to override config or not', () => {
it('user answered with "Yes"', async () => {
prompts.mockReturnValueOnce({continue: true}).mockReturnValueOnce({});

await init(resolveFromFixture('has_jest_config_file'));
await init(resolveFromFixture(`has_jest_config_file_${extension}`));

expect(prompts.mock.calls[0][0]).toMatchSnapshot();
expect(prompts.mock.calls[0][0]).toMatchSnapshot();

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

expect(writtenJestConfig).toBeDefined();
});
expect(writtenJestConfig).toBeDefined();
});

it('user answered with "No"', async () => {
prompts.mockReturnValueOnce({continue: false});
it('user answered with "No"', async () => {
prompts.mockReturnValueOnce({continue: false});

await init(resolveFromFixture('has_jest_config_file'));
// return after first prompt
expect(prompts).toHaveBeenCalledTimes(1);
await init(resolveFromFixture(`has_jest_config_file_${extension}`));
// return after first prompt
expect(prompts).toHaveBeenCalledTimes(1);
});
});
});
});
}),
);

describe('has jest config in package.json', () => {
it('should ask the user whether to override config or not', async () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/jest-cli/src/init/constants.ts
Expand Up @@ -6,4 +6,12 @@
*/

export const PACKAGE_JSON = 'package.json';
export const JEST_CONFIG = 'jest.config.js';
export const JEST_CONFIG_BASE_NAME = 'jest.config';
export const JEST_CONFIG_EXT_CJS = '.cjs';
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_CJS,
JEST_CONFIG_EXT_JSON,
]);
30 changes: 23 additions & 7 deletions packages/jest-cli/src/init/index.ts
Expand Up @@ -12,7 +12,13 @@ import prompts = require('prompts');
import {sync as realpath} from 'realpath-native';
import defaultQuestions, {testScriptQuestion} from './questions';
import {MalformedPackageJsonError, NotFoundPackageJsonError} from './errors';
import {JEST_CONFIG, PACKAGE_JSON} from './constants';
import {
JEST_CONFIG_BASE_NAME,
JEST_CONFIG_EXT_CJS,
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';
Expand All @@ -24,18 +30,18 @@ type PromptsResults = {
scripts: boolean;
};

const getConfigFilename = (ext: string) => JEST_CONFIG_BASE_NAME + ext;

export default async (rootDir: string = realpath(process.cwd())) => {
// prerequisite checks
const projectPackageJsonPath: string = path.join(rootDir, PACKAGE_JSON);
const jestConfigPath: string = path.join(rootDir, JEST_CONFIG);

if (!fs.existsSync(projectPackageJsonPath)) {
throw new NotFoundPackageJsonError(rootDir);
}

const questions = defaultQuestions.slice(0);
let hasJestProperty: boolean = false;
let hasJestConfig: boolean = false;
let projectPackageJson: ProjectPackageJson;

try {
Expand All @@ -50,11 +56,21 @@ export default async (rootDir: string = realpath(process.cwd())) => {
hasJestProperty = true;
}

if (fs.existsSync(jestConfigPath)) {
hasJestConfig = true;
}
const existingJestConfigPath = JEST_CONFIG_EXT_ORDER.find(ext =>
fs.existsSync(path.join(rootDir, getConfigFilename(ext))),
);
const jestConfigPath =
existingJestConfigPath ||
path.join(
rootDir,
getConfigFilename(
projectPackageJson.type === 'module'
thymikee marked this conversation as resolved.
Show resolved Hide resolved
? JEST_CONFIG_EXT_CJS
: JEST_CONFIG_EXT_JS,
),
);

if (hasJestProperty || hasJestConfig) {
if (hasJestProperty || existingJestConfigPath) {
const result: {continue: boolean} = await prompts({
initial: true,
message:
Expand Down
1 change: 1 addition & 0 deletions packages/jest-cli/src/init/types.ts
Expand Up @@ -10,4 +10,5 @@ import {Config} from '@jest/types';
export type ProjectPackageJson = {
jest?: Partial<Config.InitialOptions>;
scripts?: Record<string, string>;
type?: 'commonjs' | 'module';
};
113 changes: 58 additions & 55 deletions packages/jest-config/src/__tests__/resolveConfigPath.test.ts
Expand Up @@ -8,6 +8,7 @@
import {tmpdir} from 'os';
import * as path from 'path';
import resolveConfigPath from '../resolveConfigPath';
import {JEST_CONFIG_EXT_ORDER} from '../constants';

const {cleanup, writeFiles} = require('../../../../e2e/Utils');

Expand All @@ -18,74 +19,76 @@ const NO_ROOT_DIR_ERROR_PATTERN = /Can\'t find a root directory/;
beforeEach(() => cleanup(DIR));
afterEach(() => cleanup(DIR));

test('file path', () => {
const relativeConfigPath = 'a/b/c/my_config.js';
const absoluteConfigPath = path.resolve(DIR, relativeConfigPath);
JEST_CONFIG_EXT_ORDER.forEach(extension => {
SimenB marked this conversation as resolved.
Show resolved Hide resolved
test(`file path with "${extension}"`, () => {
const relativeConfigPath = `a/b/c/my_config${extension}`;
const absoluteConfigPath = path.resolve(DIR, relativeConfigPath);

writeFiles(DIR, {[relativeConfigPath]: ''});
writeFiles(DIR, {[relativeConfigPath]: ''});

// absolute
expect(resolveConfigPath(absoluteConfigPath, DIR)).toBe(absoluteConfigPath);
expect(() => resolveConfigPath('/does_not_exist', DIR)).toThrowError(
NO_ROOT_DIR_ERROR_PATTERN,
);

// relative
expect(resolveConfigPath(relativeConfigPath, DIR)).toBe(absoluteConfigPath);
expect(() => resolveConfigPath('does_not_exist', DIR)).toThrowError(
NO_ROOT_DIR_ERROR_PATTERN,
);
});
// absolute
expect(resolveConfigPath(absoluteConfigPath, DIR)).toBe(absoluteConfigPath);
expect(() => resolveConfigPath('/does_not_exist', DIR)).toThrowError(
NO_ROOT_DIR_ERROR_PATTERN,
);

test('directory path', () => {
const relativePackageJsonPath = 'a/b/c/package.json';
const absolutePackageJsonPath = path.resolve(DIR, relativePackageJsonPath);
const relativeJestConfigPath = 'a/b/c/jest.config.js';
const absoluteJestConfigPath = path.resolve(DIR, relativeJestConfigPath);
// relative
expect(resolveConfigPath(relativeConfigPath, DIR)).toBe(absoluteConfigPath);
expect(() => resolveConfigPath('does_not_exist', DIR)).toThrowError(
NO_ROOT_DIR_ERROR_PATTERN,
);
});

writeFiles(DIR, {'a/b/c/some_random_file.js': ''});
test(`directory path with "${extension}"`, () => {
const relativePackageJsonPath = 'a/b/c/package.json';
const absolutePackageJsonPath = path.resolve(DIR, relativePackageJsonPath);
const relativeJestConfigPath = `a/b/c/jest.config${extension}`;
const absoluteJestConfigPath = path.resolve(DIR, relativeJestConfigPath);

// no configs yet. should throw
expect(() =>
// absolute
resolveConfigPath(path.dirname(absoluteJestConfigPath), DIR),
).toThrowError(ERROR_PATTERN);
writeFiles(DIR, {[`a/b/c/some_random_file${extension}`]: ''});

expect(() =>
// relative
resolveConfigPath(path.dirname(relativeJestConfigPath), DIR),
).toThrowError(ERROR_PATTERN);
// no configs yet. should throw
expect(() =>
// absolute
resolveConfigPath(path.dirname(absoluteJestConfigPath), DIR),
).toThrowError(ERROR_PATTERN);

writeFiles(DIR, {[relativePackageJsonPath]: ''});
expect(() =>
// relative
resolveConfigPath(path.dirname(relativeJestConfigPath), DIR),
).toThrowError(ERROR_PATTERN);

// absolute
expect(resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR)).toBe(
absolutePackageJsonPath,
);
writeFiles(DIR, {[relativePackageJsonPath]: ''});

// relative
expect(resolveConfigPath(path.dirname(relativePackageJsonPath), DIR)).toBe(
absolutePackageJsonPath,
);
// absolute
expect(resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR)).toBe(
absolutePackageJsonPath,
);

writeFiles(DIR, {[relativeJestConfigPath]: ''});
// relative
expect(resolveConfigPath(path.dirname(relativePackageJsonPath), DIR)).toBe(
absolutePackageJsonPath,
);

// jest.config.js takes presedence
writeFiles(DIR, {[relativeJestConfigPath]: ''});

// absolute
expect(resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR)).toBe(
absoluteJestConfigPath,
);
// jest.config.js takes presedence

// relative
expect(resolveConfigPath(path.dirname(relativePackageJsonPath), DIR)).toBe(
absoluteJestConfigPath,
);
// absolute
expect(resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR)).toBe(
absoluteJestConfigPath,
);

expect(() => {
resolveConfigPath(
path.join(path.dirname(relativePackageJsonPath), 'j/x/b/m/'),
DIR,
// relative
expect(resolveConfigPath(path.dirname(relativePackageJsonPath), DIR)).toBe(
absoluteJestConfigPath,
);
}).toThrowError(NO_ROOT_DIR_ERROR_PATTERN);

expect(() => {
resolveConfigPath(
path.join(path.dirname(relativePackageJsonPath), 'j/x/b/m/'),
DIR,
);
}).toThrowError(NO_ROOT_DIR_ERROR_PATTERN);
});
});
10 changes: 9 additions & 1 deletion packages/jest-config/src/constants.ts
Expand Up @@ -11,4 +11,12 @@ export const NODE_MODULES = path.sep + 'node_modules' + path.sep;
export const DEFAULT_JS_PATTERN = '^.+\\.[jt]sx?$';
export const DEFAULT_REPORTER_LABEL = 'default';
export const PACKAGE_JSON = 'package.json';
export const JEST_CONFIG = 'jest.config.js';
export const JEST_CONFIG_BASE_NAME = 'jest.config';
export const JEST_CONFIG_EXT_CJS = '.cjs';
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_CJS,
JEST_CONFIG_EXT_JSON,
]);
4 changes: 2 additions & 2 deletions packages/jest-config/src/index.ts
Expand Up @@ -20,6 +20,7 @@ export {default as deprecationEntries} from './Deprecated';
export {replaceRootDirInPath} from './utils';
export {default as defaults} from './Defaults';
export {default as descriptions} from './Descriptions';
import {JEST_CONFIG_EXT_ORDER} from './constants';

type ReadConfig = {
configPath: Config.Path | null | undefined;
Expand Down Expand Up @@ -303,8 +304,7 @@ export function readConfigs(
typeof root === 'string' &&
fs.existsSync(root) &&
!fs.lstatSync(root).isDirectory() &&
!root.endsWith('.js') &&
!root.endsWith('.json')
!JEST_CONFIG_EXT_ORDER.some(ext => root.endsWith(ext))
) {
return false;
}
Expand Down