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 the jest.config.ts configuration file #10564

Merged
merged 39 commits into from Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
dcc71bb
feat: Add support for the `jest.config.ts` configuration file
Gamote Sep 29, 2020
e7b0573
chore: The `jest.config.ts` documentation was updated
Gamote Sep 29, 2020
a2c9fdc
chore: The `jest.config.ts` support was specified in the CHANGELOG.md
Gamote Sep 29, 2020
2b5d97a
chore: Documentation updated to encourage the usage of TS annotations
Gamote Sep 29, 2020
7f5a39d
Errors fixed for tests and prettier
Gamote Sep 29, 2020
0b68052
chore: Use 'ts-node' as peer dependency instead of 'typescript'
Gamote Sep 29, 2020
2ef46e2
chore: Documentation updated to specify the 'ts-node' peer dependency…
Gamote Sep 29, 2020
9bc4504
fix: Prettier errors
Gamote Sep 29, 2020
5674532
fix: Remove console logs to avoid them to be captured in snapshots
Gamote Sep 29, 2020
de5766b
fix: Remove console logs to avoid them to be captured in snapshots
Gamote Sep 29, 2020
c9069fd
fix: Added 'testEnvironment' in the `jest.config.ts` tests to fix the…
Gamote Sep 30, 2020
2cf2933
chore: `interopRequireDefault` was moved to save an import
Gamote Sep 30, 2020
144c341
chore: better comments for `readConfigFileAndSetRootDir`
Gamote Sep 30, 2020
8fa7b61
chore: indicate that `ts-node` is optional via 'peerDependenciesMeta'
Gamote Sep 30, 2020
d8724e6
chore: Updated documentation
Gamote Sep 30, 2020
a5033aa
chore: tests was added to be sure that the types are checked in the `…
Gamote Oct 2, 2020
4d49437
chore: Update the fixture for `jest.config.ts` to use 'default'
Gamote Oct 2, 2020
50623f4
Merge branch 'master' into feature/allow-ts-config-file
Gamote Oct 12, 2020
e0b5270
chore: (ts-node) allow for majors higher than 9
Gamote Oct 12, 2020
0d6f2ca
chore: revert changes in the 26.4 documentation
Gamote Oct 12, 2020
a291af2
chore: updated the return of 'loadTSConfigFile'
Gamote Oct 12, 2020
5d4065e
chore: replace local 'interopRequireDefault' with the one defined in …
Gamote Oct 12, 2020
ee9569b
chore: handle the case in which config is a function which imports mo…
Gamote Oct 12, 2020
91de77a
chore: let the consumers to deal with ts-node errors
Gamote Oct 12, 2020
2f16544
chore: docs comments were updated
Gamote Oct 12, 2020
b551b29
fix: tests were fixed after 'readConfigFileAndSetRootDir' updates
Gamote Oct 12, 2020
4ec1eb1
chore: 'jest-environment-jsdom-fifteen' was replaced with 'jest-envir…
Gamote Oct 12, 2020
7d6f815
chore: ask the user if he want's to use the Typescript configuration …
Gamote Oct 12, 2020
9bf4a0c
chore: use the local 'jest-environment-node'
Gamote Oct 12, 2020
a69c677
fix: use `jest-util` instead of `jest-util/build`
Gamote Oct 12, 2020
a3b7210
chore: add type annotation for the 'ts-node' register variable
Gamote Oct 12, 2020
8b678d9
chore: make Typescript override module
Gamote Oct 12, 2020
729f6dc
chore: 'jest-environment-node' was removed from the root `package.json`
Gamote Oct 12, 2020
7c02d1c
chore: added `ts-node` as dev dependency in the `jest-config`
Gamote Oct 12, 2020
9aa8d53
chores:
Gamote Oct 12, 2020
55169aa
chore: move `typescript` and `ts-node` from 'dependencies' to 'devDep…
Gamote Oct 12, 2020
b614995
fix: jest.config.ts test > user answered with "No"
Gamote Oct 12, 2020
3fb9994
chore: use multiline string instead of manual newlines
Gamote Oct 12, 2020
6336d36
chore: better naming for the 'configDocMessage': 'configHeaderMessage'
Gamote Oct 12, 2020
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-cli, jest-config]` Add support for the `jest.config.ts` configuration file ([#10564](https://github.com/facebook/jest/pull/10564))

### Fixes

### Chore & Maintenance
Expand Down
26 changes: 23 additions & 3 deletions 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|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:
Jest's configuration can be defined in the `package.json` file of your project, or through a `jest.config.js`, or `jest.config.ts` file or through the `--config <path/to/file.js|ts|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 All @@ -18,19 +18,39 @@ Or through JavaScript:

```js
// jest.config.js
//Sync object
// Sync object
module.exports = {
verbose: true,
};

//Or async function
// Or async function
module.exports = async () => {
return {
verbose: true,
};
};
```

Or through TypeScript (if `ts-node` is installed):

```ts
// jest.config.ts
import type {Config} from '@jest/types';

// Sync object
const config: Config.InitialOptions = {
verbose: true,
};
export default config;

// Or async function
export default async (): Promise<Config.InitialOptions> => {
return {
verbose: true,
};
};
```

Please keep in mind that the resulting configuration must be JSON-serializable.

When using the `--config` option, the JSON file must not contain a "jest" key:
Expand Down
36 changes: 36 additions & 0 deletions e2e/__tests__/__snapshots__/jest.config.ts.test.ts.snap
@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`traverses directory tree up until it finds jest.config 1`] = `
console.log
<<REPLACED>>/jest.config.ts/some/nested/directory

at Object.log (__tests__/a-giraffe.js:3:27)

`;

exports[`traverses directory tree up until it finds jest.config 2`] = `
PASS ../../../__tests__/a-giraffe.js
✓ giraffe
✓ abc
`;

exports[`traverses directory tree up until it finds jest.config 3`] = `
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
`;

exports[`works with jest.config.ts 1`] = `
PASS __tests__/a-giraffe.js
✓ giraffe
`;

exports[`works with jest.config.ts 2`] = `
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
`;
83 changes: 83 additions & 0 deletions e2e/__tests__/jest.config.ts.test.ts
@@ -0,0 +1,83 @@
/**
Gamote marked this conversation as resolved.
Show resolved Hide resolved
* 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 * as path from 'path';
import {wrap} from 'jest-snapshot-serializer-raw';
import runJest from '../runJest';
import {cleanup, extractSummary, writeFiles} from '../Utils';

const DIR = path.resolve(__dirname, '../jest.config.ts');

beforeEach(() => cleanup(DIR));
afterAll(() => cleanup(DIR));

test('works with jest.config.ts', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': `test('giraffe', () => expect(1).toBe(1));`,
'jest.config.ts': `export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false']);
const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(wrap(rest)).toMatchSnapshot();
expect(wrap(summary)).toMatchSnapshot();
});

test('traverses directory tree up until it finds jest.config', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': `
const slash = require('slash');
test('giraffe', () => expect(1).toBe(1));
test('abc', () => console.log(slash(process.cwd())));
`,
'jest.config.ts': `export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};`,
'package.json': '{}',
'some/nested/directory/file.js': '// nothing special',
});

const {stderr, exitCode, stdout} = runJest(
path.join(DIR, 'some', 'nested', 'directory'),
['-w=1', '--ci=false'],
{skipPkgJsonCheck: true},
);

// Snapshot the console.loged `process.cwd()` and make sure it stays the same
expect(
wrap(stdout.replace(/^\W+(.*)e2e/gm, '<<REPLACED>>')),
).toMatchSnapshot();

const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(wrap(rest)).toMatchSnapshot();
expect(wrap(summary)).toMatchSnapshot();
});

test('it does type check the config', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': `test('giraffe', () => expect(1).toBe(1));`,
'jest.config.ts': `export default { testTimeout: "10000" }`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false']);
expect(stderr).toMatch('must be of type');
expect(exitCode).toBe(1);
});

test('invalid JS in jest.config.ts', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': `test('giraffe', () => expect(1).toBe(1));`,
'jest.config.ts': `export default i'll break this file yo`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false']);
expect(stderr).toMatch('TSError: ⨯ Unable to compile TypeScript:');
expect(exitCode).toBe(1);
});
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -77,6 +77,7 @@
"strip-ansi": "^6.0.0",
"tempy": "^0.6.0",
"throat": "^5.0.0",
"ts-node": "^9.0.0",
"type-fest": "^0.16.0",
"typescript": "^4.0.2",
"which": "^2.0.1"
Expand Down
4 changes: 2 additions & 2 deletions packages/jest-cli/src/__tests__/cli/args.test.ts
Expand Up @@ -82,13 +82,13 @@ describe('check', () => {
it('raises an exception if config is not a valid JSON string', () => {
const argv = {config: 'x:1'} as Config.Argv;
expect(() => check(argv)).toThrow(
'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .mjs, .cjs, .json',
'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .json',
);
});

it('raises an exception if config is not a supported file type', () => {
const message =
'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .mjs, .cjs, .json';
'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .json';

expect(() => check({config: 'jest.configjs'} as Config.Argv)).toThrow(
message,
Expand Down
Expand Up @@ -45,9 +45,81 @@ Object {
}
`;

exports[`init has-jest-config-file-ts 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 using jest.config.ts ask the user whether he wants to use Typescript or not user answered with "Yes" 1`] = `
Array [
Object {
"initial": true,
"message": "Would you like to use Jest when running \\"test\\" script in \\"package.json\\"?",
"name": "scripts",
"type": "confirm",
},
Object {
"initial": false,
"message": "Would you like to use Typescript for the configuration file?",
"name": "useTypescript",
"type": "confirm",
},
Object {
"choices": Array [
Object {
"title": "node",
"value": "node",
},
Object {
"title": "jsdom (browser-like)",
"value": "jsdom",
},
],
"initial": 0,
"message": "Choose the test environment that will be used for testing",
"name": "environment",
"type": "select",
},
Object {
"initial": false,
"message": "Do you want Jest to add coverage reports?",
"name": "coverage",
"type": "confirm",
},
Object {
"choices": Array [
Object {
"title": "v8",
"value": "v8",
},
Object {
"title": "babel",
"value": "babel",
},
],
"initial": 0,
"message": "Which provider should be used to instrument code for coverage?",
"name": "coverageProvider",
"type": "select",
},
Object {
"initial": false,
"message": "Automatically clear mock calls and instances between every test?",
"name": "clearMocks",
"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
"/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/en/configuration.html
*/

module.exports = {
// All imported modules in your tests should be mocked automatically
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 {};
Gamote marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1 @@
{}
@@ -0,0 +1 @@
{}
30 changes: 29 additions & 1 deletion packages/jest-cli/src/init/__tests__/init.test.js
Expand Up @@ -66,7 +66,7 @@ describe('init', () => {
expect(writtenJestConfigFilename.endsWith('.mjs')).toBe(true);

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

Expand Down Expand Up @@ -193,6 +193,34 @@ describe('init', () => {
},
);

describe('project using jest.config.ts', () => {
describe('ask the user whether he wants to use Typescript or not', () => {
it('user answered with "Yes"', async () => {
prompts.mockReturnValueOnce({useTypescript: true});

await init(resolveFromFixture('test_generated_jest_config_ts'));

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

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

expect(jestConfigFileName).toContain('/jest.config.ts');
expect(writtenJestConfig.split('\n')[5]).toBe('export default {');
});

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

await init(resolveFromFixture('test_generated_jest_config_ts'));

const jestConfigFileName = fs.writeFileSync.mock.calls[0][0];

expect(jestConfigFileName).not.toContain('jest.config.ts');
});
});
});

describe('has jest config in package.json', () => {
it('should ask the user whether to override config or not', async () => {
prompts.mockReturnValueOnce({continue: true}).mockReturnValueOnce({});
Expand Down
24 changes: 20 additions & 4 deletions packages/jest-cli/src/init/generate_config_file.ts
Expand Up @@ -35,7 +35,13 @@ const generateConfigFile = (
results: Record<string, unknown>,
generateEsm = false,
): string => {
const {coverage, coverageProvider, clearMocks, environment} = results;
const {
useTypescript,
coverage,
coverageProvider,
clearMocks,
environment,
} = results;

const overrides: Record<string, unknown> = {};

Expand Down Expand Up @@ -81,10 +87,20 @@ const generateConfigFile = (
}
}

const configHeaderMessage = `/*
* For a detailed explanation regarding each configuration property${
useTypescript ? ' and type check' : ''
}, visit:
* https://jestjs.io/docs/en/configuration.html
*/

`;

return (
'// For a detailed explanation regarding each configuration property, visit:\n' +
'// https://jestjs.io/docs/en/configuration.html\n\n' +
(generateEsm ? 'export default {\n' : 'module.exports = {\n') +
configHeaderMessage +
(useTypescript || generateEsm
? 'export default {\n'
Gamote marked this conversation as resolved.
Show resolved Hide resolved
: 'module.exports = {\n') +
properties.join('\n') +
'};\n'
);
Expand Down