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(jest, jest-config): add defineConfig() helper #12801

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
YARN_ENABLE_SCRIPTS: false
run: yarn --immutable

typecheck:
name: Running TypeScript compiler
ts-compatibility:
name: TS Compatibility
runs-on: ubuntu-latest
needs: prepare-yarn-cache

Expand All @@ -53,13 +53,15 @@ jobs:
run: yarn --immutable
- name: build
run: yarn build
- name: test typings
run: yarn test-types
- name: ts integration
run: yarn test-ts --selectProjects ts-integration
- name: type tests
run: yarn test-ts --selectProjects type-tests
- name: verify TypeScript@4.3 compatibility
run: yarn verify-old-ts

lint:
name: Running Lint
name: Lint
runs-on: ubuntu-latest
needs: prepare-yarn-cache

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest, jest-config]` Add `defineConfig()` type helper and expose `JestConfig` type ([#12801](https://github.com/facebook/jest/pull/12801))
- `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826))

### Fixes
Expand Down
102 changes: 67 additions & 35 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,110 @@ 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`, 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:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

```json
{
"name": "my-project",
"jest": {
"verbose": true
}
}
```
The Jest philosophy is to work great by default, but sometimes you just need more configuration power.

It is recommended to define the configuration in a dedicated JavaScript, TypeScript or JSON file. The file will be discovered automatically, if it is named `jest.config.js|ts|cjs|mjs|json`. You can use [`--config`](CLI.md#--configpath) flag to pass an explicit path to the file.

:::note

Keep in mind that the resulting configuration object must always be JSON-serializable.

:::

Or through JavaScript:
The configuration file should simply export an object or a function returning an object:

```js title="jest.config.js"
// Sync object
/** @type {import('@jest/types').Config.InitialOptions} */
const config = {
module.exports = {
verbose: true,
};

module.exports = config;

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

Or through TypeScript (if `ts-node` is installed):
Optionally you can use `defineConfig()` helper for type definitions and autocompletion:

<Tabs groupId="examples">
<TabItem value="js" label="JavaScript">

```js title="jest.config.js"
const {defineConfig} = require('jest');

module.exports = defineConfig({
verbose: true,
});

// or async function
module.exports = defineConfig(async () => {
const verbose = await asyncGetVerbose();

return {verbose};
});
```

</TabItem>

<TabItem value="ts" label="TypeScript">

```ts title="jest.config.ts"
import type {Config} from '@jest/types';
import {JestConfig, defineConfig} from 'jest';

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

// Or async function
export default async (): Promise<Config.InitialOptions> => {
return {
verbose: true,
};
};
// or async function
export default defineConfig(async () => {
const verbose: JestConfig['verbose'] = await asyncGetVerbose();

return {verbose};
});
```

Please keep in mind that the resulting configuration must be JSON-serializable.
</TabItem>
</Tabs>

:::tip

When using the `--config` option, the JSON file must not contain a "jest" key:
Jest requires [`ts-node`](https://github.com/TypeStrong/ts-node) to be able to read TypeScript configuration files. Make sure it is installed in your project.

```json
:::

The configuration can be stored in a JSON file as a plain object:

```json title="jest.config.json"
{
"bail": 1,
"verbose": true
}
```

## Options
Alternatively Jest's configuration can be defined through the `"jest"` key in the `package.json` of your project:

These options let you control Jest's behavior in your `package.json` file. The Jest philosophy is to work great by default, but sometimes you just need more configuration power.
```json title="package.json"
{
"name": "my-project",
"jest": {
"verbose": true
}
}
```

### Defaults
## Options

You can retrieve Jest's default options to expand them if needed:

```js title="jest.config.js"
const {defaults} = require('jest-config');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking. Perhaps it would be better to expose defineJestConfig(), JestConfig and jestDefaults from 'jest'? Or better from 'jest-config'? It is nice to install only jest, in the other hand these are just devDependencies.


module.exports = {
// ...
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'],
// ...
};
Expand Down
47 changes: 47 additions & 0 deletions e2e/__tests__/defineConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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 {onNodeVersions} from '@jest/test-utils';
import {getConfig} from '../runJest';

const DIR = path.resolve(__dirname, '../define-config');

test('works with object config exported from CJS file', () => {
const {configs, globalConfig} = getConfig(path.join(DIR, 'cjs-object'));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('cjs-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from CJS file', () => {
const {configs, globalConfig} = getConfig(path.join(DIR, 'cjs-function'));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('cjs-async-function-config');
expect(globalConfig.verbose).toBe(true);
});

// The versions where vm.Module exists and commonjs with "exports" is not broken
onNodeVersions('>=12.16.0', () => {
test('works with object config exported from ESM file', () => {
const {configs, globalConfig} = getConfig(path.join(DIR, 'esm-object'));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('esm-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from ESM file', () => {
const {configs, globalConfig} = getConfig(path.join(DIR, 'esm-function'));

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('esm-async-function-config');
expect(globalConfig.verbose).toBe(true);
});
});
37 changes: 8 additions & 29 deletions e2e/__tests__/esmConfigFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

import {resolve} from 'path';
import execa = require('execa');
import {existsSync} from 'graceful-fs';
import {onNodeVersions} from '@jest/test-utils';
import {getConfig} from '../runJest';

Expand Down Expand Up @@ -48,33 +45,15 @@ onNodeVersions('>=12.17.0', () => {
});
});

describe('typescript', () => {
beforeAll(async () => {
// the typescript config test needs `@jest/types` to be built
const cwd = resolve(__dirname, '../../');
const typesPackageDirectory = 'packages/jest-types';

const indexDTsFile = resolve(
cwd,
typesPackageDirectory,
'build/index.d.ts',
);

if (!existsSync(indexDTsFile)) {
await execa('tsc', ['-b', typesPackageDirectory], {cwd});
}
}, 360_000);

test('reads config from ts file when package.json#type=module', () => {
const {configs} = getConfig('esm-config/ts', [], {
skipPkgJsonCheck: true,
});
test('reads config from ts file when package.json#type=module', () => {
const {configs} = getConfig('esm-config/ts', [], {
skipPkgJsonCheck: true,
});

expect(configs).toHaveLength(1);
expect(configs[0].displayName).toEqual({
color: 'white',
name: 'Config from ts file',
});
expect(configs).toHaveLength(1);
expect(configs[0].displayName).toEqual({
color: 'white',
name: 'Config from ts file',
});
});
});
55 changes: 55 additions & 0 deletions e2e/__tests__/tsIntegration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* 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 {onNodeVersions} from '@jest/test-utils';
import {getConfig} from '../runJest';

const DIR = path.resolve(__dirname, '../ts-node');

test('works with object config exported from TS file', () => {
const {configs, globalConfig} = getConfig(
path.join(DIR, 'defineConfig-object'),
);

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from TS file', () => {
const {configs, globalConfig} = getConfig(
path.join(DIR, 'defineConfig-function'),
);

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-async-function-config');
expect(globalConfig.verbose).toBe(true);
});

// The versions where vm.Module exists and commonjs with "exports" is not broken
onNodeVersions('>=12.16.0', () => {
test('works with object config exported from TS file when package.json#type=module', () => {
const {configs, globalConfig} = getConfig(
path.join(DIR, 'defineConfig-esm-object'),
);

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-object-config');
expect(globalConfig.verbose).toBe(true);
});

test('works with function config exported from TS file when package.json#type=module', () => {
const {configs, globalConfig} = getConfig(
path.join(DIR, 'defineConfig-esm-function'),
);

expect(configs).toHaveLength(1);
expect(configs[0].displayName?.name).toBe('ts-async-function-config');
expect(globalConfig.verbose).toBe(true);
});
});
10 changes: 10 additions & 0 deletions e2e/define-config/cjs-function/__tests__/dummy.test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
expect('true').toBeTruthy();
});
21 changes: 21 additions & 0 deletions e2e/define-config/cjs-function/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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 {defineConfig} = require('jest');

async function getVerbose() {
return true;
}

module.exports = defineConfig(async () => {
const verbose = await getVerbose();

return {
displayName: 'cjs-async-function-config',
verbose,
};
});
3 changes: 3 additions & 0 deletions e2e/define-config/cjs-function/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "define-config-cjs-function"
}
10 changes: 10 additions & 0 deletions e2e/define-config/cjs-object/__tests__/dummy.test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
expect('true').toBeTruthy();
});
Loading