diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0e4a8b3327..ea6b50006ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest]` Expose `Config` type ([#12848](https://github.com/facebook/jest/pull/12848)) - `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826)) ### Fixes diff --git a/docs/Configuration.md b/docs/Configuration.md index f0c971fb9c24..b6067a99a508 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -3,29 +3,57 @@ 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 ` 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. -Or through JavaScript: +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|mjs|cjs|json`. You can use [`--config`](CLI.md#--configpath) flag to pass an explicit path to the file. -```js title="jest.config.js" -// Sync object -/** @type {import('@jest/types').Config.InitialOptions} */ +:::note + +Keep in mind that the resulting configuration object must always be JSON-serializable. + +::: + +The configuration file should simply export an object: + + + + +```js +/** @type {import('jest').Config} */ const config = { verbose: true, }; module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + verbose: true, +}; -// Or async function +export default config; +``` + + + + +Or a function returning an object: + + + + +```js +/** @returns {Promise} */ module.exports = async () => { return { verbose: true, @@ -33,53 +61,89 @@ module.exports = async () => { }; ``` -Or through TypeScript (if `ts-node` is installed): + -```ts title="jest.config.ts" -import type {Config} from '@jest/types'; + -// Sync object -const config: Config.InitialOptions = { - verbose: true, -}; -export default config; +```ts +import type {Config} from 'jest'; -// Or async function -export default async (): Promise => { +export default async (): Promise => { return { verbose: true, }; }; ``` -Please keep in mind that the resulting configuration must be JSON-serializable. + + + +:::tip -When using the `--config` option, the JSON file must not contain a "jest" key: +To read TypeScript configuration files Jest requires [`ts-node`](https://npmjs.com/package/ts-node). Make sure it is installed in your project. -```json +::: + +The configuration also can be stored in a JSON file as a plain object: + +```json title="jest.config.json" { "bail": 1, "verbose": true } ``` +Alternatively Jest's configuration can be defined through the `"jest"` key in the `package.json` of your project: + +```json title="package.json" +{ + "name": "my-project", + "jest": { + "verbose": true + } +} +``` + ## Options -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. +:::info -### Defaults +You can retrieve Jest's defaults from `jest-config` to extend them if needed: -You can retrieve Jest's default options to expand them if needed: + + -```js title="jest.config.js" +```js const {defaults} = require('jest-config'); -module.exports = { - // ... - moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], - // ... + +/** @type {import('jest').Config} */ +const config = { + moduleFileExtensions: [...defaults.moduleFileExtensions, 'mts', 'cts'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; +import {defaults} from 'jest-config'; + +const config: Config = { + moduleFileExtensions: [...defaults.moduleFileExtensions, 'mts'], }; + +export default config; ``` + + + +::: + import TOCInline from '@theme/TOCInline'; @@ -98,15 +162,12 @@ Example: ```js title="utils.js" export default { - authorize: () => { - return 'token'; - }, + authorize: () => 'token', isAuthorized: secret => secret === 'wizard', }; ``` -```js -//__tests__/automocking.test.js +```js title="__tests__/automock.test.js" import utils from '../utils'; test('if utils mocked automatically', () => { @@ -172,18 +233,43 @@ Default: `undefined` An array of [glob patterns](https://github.com/micromatch/micromatch) indicating a set of files for which coverage information should be collected. If a file matches the specified glob pattern, coverage information will be collected for it even if no tests exist for this file and it's never required in the test suite. -Example: + + -```json -{ - "collectCoverageFrom": [ - "**/*.{js,jsx}", - "!**/node_modules/**", - "!**/vendor/**" - ] -} +```js +/** @type {import('jest').Config} */ +const config = { + collectCoverageFrom: [ + '**/*.{js,jsx}', + '!**/node_modules/**', + '!**/vendor/**', + ], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + collectCoverageFrom: [ + '**/*.{js,jsx}', + '!**/node_modules/**', + '!**/vendor/**', + ], +}; + +export default config; ``` + + + This will collect coverage information for all the files inside the project's `rootDir`, except the ones that match `**/node_modules/**` or `**/vendor/**`. :::tip @@ -250,12 +336,35 @@ Setting this option overwrites the default values. Add `"text"` or `"text-summar Additional options can be passed using the tuple form. For example, you may hide coverage report lines for all fully-covered files: -```json -{ - "coverageReporters": ["clover", "json", "lcov", ["text", {"skipFull": true}]] -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + coverageReporters: ['clover', 'json', 'lcov', ['text', {skipFull: true}]], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + coverageReporters: ['clover', 'json', 'lcov', ['text', {skipFull: true}]], +}; + +export default config; ``` + + + For more information about the options object shape refer to `CoverageReporterWithOptions` type in the [type definitions](https://github.com/facebook/jest/tree/main/packages/jest-types/src/Config.ts). ### `coverageThreshold` \[object] @@ -266,55 +375,122 @@ This will be used to configure minimum threshold enforcement for coverage result For example, with the following configuration jest will fail if there is less than 80% branch, line, and function coverage, or if there are more than 10 uncovered statements: -```json -{ - ... - "jest": { - "coverageThreshold": { - "global": { - "branches": 80, - "functions": 80, - "lines": 80, - "statements": -10 - } - } - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: -10, + }, + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: -10, + }, + }, +}; + +export default config; ``` + + + If globs or paths are specified alongside `global`, coverage data for matching paths will be subtracted from overall coverage and thresholds will be applied independently. Thresholds for globs are applied to all files matching the glob. If the file specified by path is not found, an error is returned. For example, with the following configuration: -```json -{ - ... - "jest": { - "coverageThreshold": { - "global": { - "branches": 50, - "functions": 50, - "lines": 50, - "statements": 50 - }, - "./src/components/": { - "branches": 40, - "statements": 40 - }, - "./src/reducers/**/*.js": { - "statements": 90 - }, - "./src/api/very-important-module.js": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": 100 - } - } - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + coverageThreshold: { + global: { + branches: 50, + functions: 50, + lines: 50, + statements: 50, + }, + './src/components/': { + branches: 40, + statements: 40, + }, + './src/reducers/**/*.js': { + statements: 90, + }, + './src/api/very-important-module.js': { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + coverageThreshold: { + global: { + branches: 50, + functions: 50, + lines: 50, + statements: 50, + }, + './src/components/': { + branches: 40, + statements: 40, + }, + './src/reducers/**/*.js': { + statements: 90, + }, + './src/api/very-important-module.js': { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}; + +export default config; ``` + + + Jest will fail if: - The `./src/components` directory has less than 40% branch or statement coverage. @@ -355,26 +531,73 @@ That module can also contain a `getCacheKey` function to generate a cache key to default: `undefined` -Allows for a label to be printed alongside a test while it is running. This becomes more useful in multi-project repositories where there can be many jest configuration files. This visually tells which project a test belongs to. Here are sample valid values. +Allows for a label to be printed alongside a test while it is running. This becomes more useful in multi-project repositories where there can be many jest configuration files. This visually tells which project a test belongs to. + + + ```js -module.exports = { +/** @type {import('jest').Config} */ +const config = { + displayName: 'CLIENT', +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { displayName: 'CLIENT', }; + +export default config; ``` -or + + + +Alternatively, an object with the properties `name` and `color` can be passed. This allows for a custom configuration of the background color of the displayName. `displayName` defaults to white when its value is a string. Jest uses [`chalk`](https://github.com/chalk/chalk) to provide the color. As such, all of the valid options for colors supported by `chalk` are also supported by Jest. + + + ```js -module.exports = { +/** @type {import('jest').Config} */ +const config = { + displayName: { + name: 'CLIENT', + color: 'blue', + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { displayName: { name: 'CLIENT', color: 'blue', }, }; + +export default config; ``` -As a secondary option, an object with the properties `name` and `color` can be passed. This allows for a custom configuration of the background color of the displayName. `displayName` defaults to white when its value is a string. Jest uses [chalk](https://github.com/chalk/chalk) to provide the color. As such, all of the valid options for colors supported by chalk are also supported by jest. + + ### `errorOnDeprecated` \[boolean] @@ -388,21 +611,41 @@ Default: `[]` Jest will run `.mjs` and `.js` files with nearest `package.json`'s `type` field set to `module` as ECMAScript Modules. If you have any other files that should run with native ESM, you need to specify their file extension here. + + + +```js +/** @type {import('jest').Config} */ +const config = { + extensionsToTreatAsEsm: ['.ts'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + extensionsToTreatAsEsm: ['.ts'], +}; + +export default config; +``` + + + + :::caution Jest's ESM support is still experimental, see [its docs for more details](ECMAScriptModules.md). ::: -```json -{ - ... - "jest": { - "extensionsToTreatAsEsm": [".ts"] - } -} -``` - ### `fakeTimers` \[object] Default: `{}` @@ -411,37 +654,88 @@ The fake timers may be useful when a piece of code sets a long timeout that we d This option provides the default configuration of fake timers for all tests. Calling `jest.useFakeTimers()` in a test file will use these options or will override them if a configuration object is passed. For example, you can tell Jest to keep the original implementation of `process.nextTick()` and adjust the limit of recursive timers that will be run: -```json -{ - "fakeTimers": { - "doNotFake": ["nextTick"], - "timerLimit": 1000 - } -} -``` + + -```js title="fakeTime.test.js" -// install fake timers for this file using the options from Jest configuration -jest.useFakeTimers(); +```js +/** @type {import('jest').Config} */ +const config = { + fakeTimers: { + doNotFake: ['nextTick'], + timerLimit: 1000, + }, +}; -test('increase the limit of recursive timers for this and following tests', () => { - jest.useFakeTimers({timerLimit: 5000}); - // ... -}); +module.exports = config; ``` -:::tip + -Instead of including `jest.useFakeTimers()` in each test file, you can enable fake timers globally for all tests: + -```json -{ - "fakeTimers": { - "enableGlobally": true - } -} +```ts +import type {Config} from 'jest'; + +const config: Config = { + fakeTimers: { + doNotFake: ['nextTick'], + timerLimit: 1000, + }, +}; + +export default config; +``` + + + + +```js title="fakeTime.test.js" +// install fake timers for this file using the options from Jest configuration +jest.useFakeTimers(); + +test('increase the limit of recursive timers for this and following tests', () => { + jest.useFakeTimers({timerLimit: 5000}); + // ... +}); +``` + +:::tip + +Instead of including `jest.useFakeTimers()` in each test file, you can enable fake timers globally for all tests in your Jest configuration: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + fakeTimers: { + enableGlobally: true, + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + fakeTimers: { + enableGlobally: true, + }, +}; + +export default config; ``` + + + ::: Configuration options: @@ -494,15 +788,41 @@ type ModernFakeTimersConfig = { For some reason you might have to use legacy implementation of fake timers. Here is how to enable it globally (additional options are not supported): -```json -{ - "fakeTimers": { - "enableGlobally": true, - "legacyFakeTimers": true - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + fakeTimers: { + enableGlobally: true, + legacyFakeTimers: true, + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + fakeTimers: { + enableGlobally: true, + legacyFakeTimers: true, + }, +}; + +export default config; ``` + + + ::: ### `forceCoverageMatch` \[array<string>] @@ -527,15 +847,35 @@ if (process.env.NODE_ENV === 'test') { You can collect coverage from those files with setting `forceCoverageMatch`. -```json -{ - ... - "jest": { - "forceCoverageMatch": ["**/*.t.js"] - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + forceCoverageMatch: ['**/*.t.js'], +}; + +module.exports = config; ``` + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + forceCoverageMatch: ['**/*.t.js'], +}; + +export default config; +``` + + + + ### `globals` \[object] Default: `{}` @@ -544,17 +884,39 @@ A set of global variables that need to be available in all test environments. For example, the following would create a global `__DEV__` variable set to `true` in all test environments: -```json -{ - ... - "jest": { - "globals": { - "__DEV__": true - } - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + globals: { + __DEV__: true, + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + globals: { + __DEV__: true, + }, +}; + +export default config; ``` + + + Note that, if you specify a global reference value (like an object or array) here, and some code mutates that value in the midst of running a test, that mutation will _not_ be persisted across test runs for other test files. In addition, the `globals` object must be json-serializable, so it can't be used to specify global functions. For that, you should use `setupFiles`. ### `globalSetup` \[string] @@ -671,13 +1033,71 @@ A number limiting the number of tests that are allowed to run at the same time w Specifies the maximum number of workers the worker-pool will spawn for running tests. In single run mode, this defaults to the number of the cores available on your machine minus one for the main thread. In watch mode, this defaults to half of the available cores on your machine to ensure Jest is unobtrusive and does not grind your machine to a halt. It may be useful to adjust this in resource limited environments like CIs but the defaults should be adequate for most use-cases. -For environments with variable CPUs available, you can use percentage based configuration: `"maxWorkers": "50%"` +For environments with variable CPUs available, you can use percentage based configuration: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + maxWorkers: '50%', +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + maxWorkers: '50%', +}; + +export default config; +``` + + + ### `moduleDirectories` \[array<string>] Default: `["node_modules"]` -An array of directory names to be searched recursively up from the requiring module's location. Setting this option will _override_ the default, if you wish to still search `node_modules` for packages include it along with any other options: `["node_modules", "bower_components"]` +An array of directory names to be searched recursively up from the requiring module's location. Setting this option will _override_ the default, if you wish to still search `node_modules` for packages include it along with any other options: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + moduleDirectories: ['node_modules', 'bower_components'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + moduleDirectories: ['node_modules', 'bower_components'], +}; + +export default config; +``` + + + ### `moduleFileExtensions` \[array<string>] @@ -699,23 +1119,53 @@ Use `` string token to refer to [`rootDir`](#rootdir-string) value if y Additionally, you can substitute captured regex groups using numbered backreferences. -Example: + + -```json -{ - "moduleNameMapper": { - "^image![a-zA-Z0-9$_-]+$": "GlobalImageStub", - "^[./a-zA-Z0-9$_-]+\\.png$": "/RelativeImageStub.js", - "module_name_(.*)": "/substituted_module_$1.js", - "assets/(.*)": [ - "/images/$1", - "/photos/$1", - "/recipes/$1" - ] - } -} +```js +/** @type {import('jest').Config} */ +const config = { + moduleNameMapper: { + '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub', + '^[./a-zA-Z0-9$_-]+\\.png$': '/RelativeImageStub.js', + 'module_name_(.*)': '/substituted_module_$1.js', + 'assets/(.*)': [ + '/images/$1', + '/photos/$1', + '/recipes/$1', + ], + }, +}; + +module.exports = config; ``` + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + moduleNameMapper: { + '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub', + '^[./a-zA-Z0-9$_-]+\\.png$': '/RelativeImageStub.js', + 'module_name_(.*)': '/substituted_module_$1.js', + 'assets/(.*)': [ + '/images/$1', + '/photos/$1', + '/recipes/$1', + ], + }, +}; + +export default config; +``` + + + + The order in which the mappings are defined matters. Patterns are checked one by one until one fits. The most specific rule should be listed first. This is true for arrays of module names as well. :::info @@ -730,13 +1180,71 @@ Default: `[]` An array of regexp pattern strings that are matched against all module paths before those paths are to be considered 'visible' to the module loader. If a given module's path matches any of the patterns, it will not be `require()`-able in the test environment. -These pattern strings match against the full path. Use the `` string token to include the path to your project's root directory to prevent it from accidentally ignoring all of your files in different environments that may have different root directories. Example: `["/build/"]`. +These pattern strings match against the full path. Use the `` string token to include the path to your project's root directory to prevent it from accidentally ignoring all of your files in different environments that may have different root directories. + + + + +```js +/** @type {import('jest').Config} */ +const config = { + modulePathIgnorePatterns: ['/build/'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + modulePathIgnorePatterns: ['/build/'], +}; + +export default config; +``` + + + ### `modulePaths` \[array<string>] Default: `[]` -An alternative API to setting the `NODE_PATH` env variable, `modulePaths` is an array of absolute paths to additional locations to search when resolving modules. Use the `` string token to include the path to your project's root directory. Example: `["/app/"]`. +An alternative API to setting the `NODE_PATH` env variable, `modulePaths` is an array of absolute paths to additional locations to search when resolving modules. Use the `` string token to include the path to your project's root directory. + + + + +```js +/** @type {import('jest').Config} */ +const config = { + modulePaths: ['/app/'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + modulePaths: ['/app/'], +}; + +export default config; +``` + + + ### `notify` \[boolean] @@ -779,22 +1287,66 @@ A preset that is used as a base for Jest's configuration. A preset should point For example, this preset `foo-bar/jest-preset.js` will be configured as follows: + + + ```js -/** @type { import('@jest/types').Config.InitialOptions } */ -module.exports = { +/** @type {import('jest').Config} */ +const config = { preset: 'foo-bar', }; + +module.exports = config; ``` -Presets may also be relative to filesystem paths. + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + preset: 'foo-bar', +}; + +export default config; +``` + + + + +Presets may also be relative to filesystem paths: + + + ```js -/** @type { import('@jest/types').Config.InitialOptions } */ -module.exports = { +/** @type {import('jest').Config} */ +const config = { + preset: './node_modules/foo-bar/jest-preset.js', +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { preset: './node_modules/foo-bar/jest-preset.js', }; + +export default config; ``` + + + :::info Note that if you also have specified [`rootDir`](#rootdir-string) that the resolution of this file will be relative to that root directory. @@ -813,85 +1365,238 @@ Default: `undefined` When the `projects` configuration is provided with an array of paths or glob patterns, Jest will run tests in all of the specified projects at the same time. This is great for monorepos or when working on multiple projects at the same time. -```json -{ - "projects": ["", "/examples/*"] -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + projects: ['', '/examples/*'], +}; + +module.exports = config; ``` + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + projects: ['', '/examples/*'], +}; + +export default config; +``` + + + + This example configuration will run Jest in the root directory as well as in every folder in the examples directory. You can have an unlimited amount of projects running in the same Jest instance. The projects feature can also be used to run multiple configurations or multiple [runners](#runner-string). For this purpose, you can pass an array of configuration objects. For example, to run both tests and ESLint (via [jest-runner-eslint](https://github.com/jest-community/jest-runner-eslint)) in the same invocation of Jest: -```json -{ - "projects": [ + + + +```js +/** @type {import('jest').Config} */ +const config = { + projects: [ { - "displayName": "test" + displayName: 'test', }, { - "displayName": "lint", - "runner": "jest-runner-eslint", - "testMatch": ["/**/*.js"] - } - ] -} + displayName: 'lint', + runner: 'jest-runner-eslint', + testMatch: ['/**/*.js'], + }, + ], +}; + +module.exports = config; ``` + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + projects: [ + { + displayName: 'test', + }, + { + displayName: 'lint', + runner: 'jest-runner-eslint', + testMatch: ['/**/*.js'], + }, + ], +}; + +export default config; +``` + + + + :::tip When using multi-project runner, it's recommended to add a `displayName` for each project. This will show the `displayName` of a project next to its tests. -::: +::: + +### `reporters` \[array<moduleName | \[moduleName, options]>] + +Default: `undefined` + +Use this configuration option to add reporters to Jest. It must be a list of reporter names, additional options can be passed to a reporter using the tuple form: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + reporters: [ + 'default', + ['/custom-reporter.js', {banana: 'yes', pineapple: 'no'}], + ], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + reporters: [ + 'default', + ['/custom-reporter.js', {banana: 'yes', pineapple: 'no'}], + ], +}; + +export default config; +``` + + + + +#### Default Reporter + +If custom reporters are specified, the default Jest reporter will be overridden. If you wish to keep it, `'default'` must be passed as a reporters name: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + reporters: [ + 'default', + ['jest-junit', {outputDirectory: 'reports', outputName: 'report.xml'}], + ], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + reporters: [ + 'default', + ['jest-junit', {outputDirectory: 'reports', outputName: 'report.xml'}], + ], +}; + +export default config; +``` + + + + +#### GitHub Actions Reporter + +If included in the list, the built-in GitHub Actions Reporter will annotate changed files with test failure messages: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + reporters: ['default', 'github-actions'], +}; + +module.exports = config; +``` + + -### `reporters` \[array<moduleName | \[moduleName, options]>] + -Default: `undefined` +```ts +import type {Config} from 'jest'; -Use this configuration option to add reporters to Jest. It must be a list of reporter names, additional options can be passed to a reporter using the tuple form: +const config: Config = { + reporters: ['default', 'github-actions'], +}; -```json -{ - "reporters": [ - "default", - ["/custom-reporter.js", {"banana": "yes", "pineapple": "no"}] - ] -} +export default config; ``` -#### Default Reporter + + -If custom reporters are specified, the default Jest reporter will be overridden. If you wish to keep it, `'default'` must be passed as a reporters name: +#### Summary Reporter -```json -{ - "reporters": [ - "default", - ["jest-junit", {"outputDirectory": "reports", "outputName": "report.xml"}] - ] -} -``` +Summary reporter prints out summary of all tests. It is a part of default reporter, hence it will be enabled if `'default'` is included in the list. For instance, you might want to use it as stand-alone reporter instead of the default one, or together with [Silent Reporter](https://github.com/rickhanlonii/jest-silent-reporter): -#### GitHub Actions Reporter + + -If included in the list, the built-in GitHub Actions Reporter will annotate changed files with test failure messages: +```js +/** @type {import('jest').Config} */ +const config = { + reporters: ['jest-silent-reporter', 'summary'], +}; -```json -{ - "reporters": ["default", "github-actions"] -} +module.exports = config; ``` -#### Summary Reporter + -Summary reporter prints out summary of all tests. It is a part of default reporter, hence it will be enabled if `'default'` is included in the list. For instance, you might want to use it as stand-alone reporter instead of the default one, or together with [Silent Reporter](https://github.com/rickhanlonii/jest-silent-reporter): + -```json -{ - "reporters": ["jest-silent-reporter", "summary"] -} +```ts +import type {Config} from 'jest'; + +const config: Config = { + reporters: ['jest-silent-reporter', 'summary'], +}; + +export default config; ``` + + + #### Custom Reporters :::tip @@ -981,13 +1686,7 @@ The `defaultResolver` passed as an option is the Jest default resolver which mig ::: -For example, if you want to respect Browserify's [`"browser"` field](https://github.com/browserify/browserify-handbook/blob/master/readme.markdown#browser-field), you can use the following configuration: - -```json -{ - "resolver": "/resolver.js" -} -``` +For example, if you want to respect Browserify's [`"browser"` field](https://github.com/browserify/browserify-handbook/blob/master/readme.markdown#browser-field), you can use the following resolver: ```js title="resolver.js" const browserResolve = require('browser-resolve'); @@ -995,6 +1694,37 @@ const browserResolve = require('browser-resolve'); module.exports = browserResolve.sync; ``` +And add it to Jest configuration: + + + + +```js +/** @type {import('jest').Config} */ +const config = { + resolver: '/resolver.js', +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + resolver: '/resolver.js', +}; + +export default config; +``` + + + + By combining `defaultResolver` and `packageFilter` we can implement a `package.json` "pre-processor" that allows us to change how the default resolver will resolve modules. For example, imagine we want to use the field `"module"` if it is present, otherwise fallback to `"main"`: ```js @@ -1096,15 +1826,35 @@ Test files run inside a [vm](https://nodejs.org/api/vm.html), which slows calls For example, if your tests call `Math` often, you can pass it by setting `sandboxInjectedGlobals`. -```json -{ - ... - "jest": { - "sandboxInjectedGlobals": ["Math"] - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + sandboxInjectedGlobals: ['Math'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + sandboxInjectedGlobals: ['Math'], +}; + +export default config; ``` + + + :::note This option has no effect if you use [native ESM](ECMAScriptModules.md). @@ -1140,12 +1890,35 @@ afterEach(() => { }); ``` + + + ```js -module.exports = { +/** @type {import('jest').Config} */ +const config = { setupFilesAfterEnv: ['/setup-jest.js'], }; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + setupFilesAfterEnv: ['/setup-matchers.js'], +}; + +export default config; ``` + + + ### `slowTestThreshold` \[number] Default: `5` @@ -1158,19 +1931,40 @@ Default: `undefined` Allows overriding specific snapshot formatting options documented in the [pretty-format readme](https://www.npmjs.com/package/pretty-format#usage-with-options), with the exceptions of `compareKeys` and `plugins`. For example, this config would have the snapshot formatter not print a prefix for "Object" and "Array": -```json -{ - "jest": { - "snapshotFormat": { - "printBasicPrototype": false - } - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + snapshotFormat: { + printBasicPrototype: false, + }, +}; + +module.exports = config; ``` + + + + ```ts -import {expect, test} from '@jest/globals'; +import type {Config} from 'jest'; + +const config: Config = { + snapshotFormat: { + printBasicPrototype: false, + }, +}; + +export default config; +``` + + + +```js title="some.test.js" test('does not show prototypes for object and array inline', () => { const object = { array: [{hello: 'Danger'}], @@ -1193,9 +1987,7 @@ Default: `undefined` The path to a module that can resolve test<->snapshot path. This config option lets you customize where Jest stores snapshot files on disk. -Example snapshot resolver module: - -```js +```js title="custom-resolver.js" module.exports = { // resolves from test to snapshot path resolveSnapshotPath: (testPath, snapshotExtension) => @@ -1220,10 +2012,7 @@ A list of paths to snapshot serializer modules Jest should use for snapshot test Jest has default serializers for built-in JavaScript types, HTML elements (Jest 20.0.0+), ImmutableJS (Jest 20.0.0+) and for React elements. See [snapshot test tutorial](TutorialReactNative.md#snapshot-test) for more information. -Example serializer module: - -```js -// my-serializer-module +```js title="custom-serializer.js" module.exports = { serialize(val, config, indentation, depth, refs, printer) { return `Pretty foo: ${printer(val.foo)}`; @@ -1237,17 +2026,37 @@ module.exports = { `printer` is a function that serializes a value using existing plugins. -To use `my-serializer-module` as a serializer, configuration would be as follows: +Add `custom-serializer` to your Jest configuration: -```json -{ - ... - "jest": { - "snapshotSerializers": ["my-serializer-module"] - } -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + snapshotSerializers: ['path/to/custom-serializer.js'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + snapshotSerializers: ['path/to/custom-serializer.js'], +}; + +export default config; ``` + + + Finally tests would look as follows: ```js @@ -1544,11 +2353,9 @@ Both `sort` and `shard` may optionally return a `Promise`. ::: -Example: - -Sort test path alphabetically. +For example, you may sort test paths alphabetically: -```js title="testSequencer.js" +```js title="custom-sequencer.js" const Sequencer = require('@jest/test-sequencer').default; class CustomSequencer extends Sequencer { @@ -1581,14 +2388,37 @@ class CustomSequencer extends Sequencer { module.exports = CustomSequencer; ``` -Use it in your Jest config file like this: +Add `custom-sequencer` to your Jest configuration: -```json -{ - "testSequencer": "path/to/testSequencer.js" -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + testSequencer: 'path/to/custom-sequencer.js', +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + testSequencer: 'path/to/custom-sequencer.js', +}; + +export default config; ``` + + + ### `testTimeout` \[number] Default: `5000` @@ -1611,13 +2441,41 @@ Keep in mind that a transformer only runs once per file unless the file has chan Remember to include the default `babel-jest` transformer explicitly, if you wish to use it alongside with additional code preprocessors: -```json -"transform": { - "\\.[jt]sx?$": "babel-jest", - "\\.css$": "some-css-transformer", -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + transform: { + '\\.[jt]sx?$': 'babel-jest', + '\\.css$': 'some-css-transformer', + }, +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + transform: { + '\\.[jt]sx?$': 'babel-jest', + '\\.css$': 'some-css-transformer', + }, +}; + +export default config; ``` + + + ::: ### `transformIgnorePatterns` \[array<string>] @@ -1628,29 +2486,76 @@ An array of regexp pattern strings that are matched against all source file path Providing regexp patterns that overlap with each other may result in files not being transformed that you expected to be transformed. For example: -```json -{ - "transformIgnorePatterns": ["/node_modules/(?!(foo|bar)/)", "/bar/"] -} + + + +```js +/** @type {import('jest').Config} */ +const config = { + transformIgnorePatterns: ['/node_modules/(?!(foo|bar)/)', '/bar/'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + transformIgnorePatterns: ['/node_modules/(?!(foo|bar)/)', '/bar/'], +}; + +export default config; ``` + + + The first pattern will match (and therefore not transform) files inside `/node_modules` except for those in `/node_modules/foo/` and `/node_modules/bar/`. The second pattern will match (and therefore not transform) files inside any path with `/bar/` in it. With the two together, files in `/node_modules/bar/` will not be transformed because it does match the second pattern, even though it was excluded by the first. Sometimes it happens (especially in React Native or TypeScript projects) that 3rd party modules are published as untranspiled code. Since all files inside `node_modules` are not transformed by default, Jest will not understand the code in these modules, resulting in syntax errors. To overcome this, you may use `transformIgnorePatterns` to allow transpiling such modules. You'll find a good example of this use case in [React Native Guide](/docs/tutorial-react-native#transformignorepatterns-customization). These pattern strings match against the full path. Use the `` string token to include the path to your project's root directory to prevent it from accidentally ignoring all of your files in different environments that may have different root directories. -Example: + + -```json -{ - "transformIgnorePatterns": [ - "/bower_components/", - "/node_modules/" - ] -} +```js +/** @type {import('jest').Config} */ +const config = { + transformIgnorePatterns: [ + '/bower_components/', + '/node_modules/', + ], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + transformIgnorePatterns: [ + '/bower_components/', + '/node_modules/', + ], +}; + +export default config; ``` + + + ### `unmockedModulePathPatterns` \[array<string>] Default: `[]` @@ -1677,14 +2582,35 @@ These patterns match against the full path. Use the `` string token to Even if nothing is specified here, the watcher will ignore changes to the version control folders (.git, .hg). Other hidden files and directories, i.e. those that begin with a dot (`.`), are watched by default. Remember to escape the dot when you add them to `watchPathIgnorePatterns` as it is a special RegExp character. -Example: + + -```json -{ - "watchPathIgnorePatterns": ["/\\.tmp/", "/bar/"] -} +```js +/** @type {import('jest').Config} */ +const config = { + watchPathIgnorePatterns: ['/\\.tmp/', '/bar/'], +}; + +module.exports = config; +``` + + + + + +```ts +import type {Config} from 'jest'; + +const config: Config = { + watchPathIgnorePatterns: ['/\\.tmp/', '/bar/'], +}; + +export default config; ``` + + + ### `watchPlugins` \[array<string | \[string, Object]>] Default: `[]` @@ -1713,13 +2639,9 @@ Whether to use [`watchman`](https://facebook.github.io/watchman/) for file crawl ### `//` \[string] -No default +This option allows comments in `package.json`. Include the comment text as the value of this key: -This option allows comments in `package.json`. Include the comment text as the value of this key anywhere in `package.json`. - -Example: - -```json +```json title="package.json" { "name": "my-project", "jest": { diff --git a/e2e/__tests__/tsIntegration.test.ts b/e2e/__tests__/tsIntegration.test.ts index 7ae2637f256c..953ee0aaa5ab 100644 --- a/e2e/__tests__/tsIntegration.test.ts +++ b/e2e/__tests__/tsIntegration.test.ts @@ -174,3 +174,163 @@ describe('when `Config` type is imported from "@jest/types"', () => { }); }); }); + +describe('when `Config` type is imported from "jest"', () => { + test('with object config exported from TS file', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {displayName: 'ts-object-config', verbose: true}; + export default config; + `, + 'package.json': '{}', + }); + + const {configs, globalConfig} = getConfig(path.join(DIR)); + + expect(configs).toHaveLength(1); + expect(configs[0].displayName?.name).toBe('ts-object-config'); + expect(globalConfig.verbose).toBe(true); + }); + + test('with function config exported from TS file', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + async function getVerbose() {return true;} + export default async (): Promise => { + const verbose: Config['verbose'] = await getVerbose(); + return {displayName: 'ts-async-function-config', verbose}; + }; + `, + 'package.json': '{}', + }); + + const {configs, globalConfig} = getConfig(path.join(DIR)); + + expect(configs).toHaveLength(1); + expect(configs[0].displayName?.name).toBe('ts-async-function-config'); + expect(globalConfig.verbose).toBe(true); + }); + + test('throws if type errors are encountered', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {testTimeout: '10000'}; + export default config; + `, + 'package.json': '{}', + }); + + const {stderr, exitCode} = runJest(DIR); + + expect(stderr).toMatch( + "jest.config.ts(2,25): error TS2322: Type 'string' is not assignable to type 'number'.", + ); + expect(exitCode).toBe(1); + }); + + test('throws if syntax errors are encountered', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {verbose: true}; + export default get config; + `, + 'package.json': '{}', + }); + + const {stderr, exitCode} = runJest(DIR); + + expect(stderr).toMatch( + "jest.config.ts(3,16): error TS2304: Cannot find name 'get'.", + ); + expect(exitCode).toBe(1); + }); + + // 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', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {displayName: 'ts-esm-object-config', verbose: true}; + export default config; + `, + 'package.json': '{"type": "module"}', + }); + + const {configs, globalConfig} = getConfig(path.join(DIR)); + + expect(configs).toHaveLength(1); + expect(configs[0].displayName?.name).toBe('ts-esm-object-config'); + expect(globalConfig.verbose).toBe(true); + }); + + test('works with function config exported from TS file when package.json#type=module', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + async function getVerbose() {return true;} + export default async (): Promise => { + const verbose: Config['verbose'] = await getVerbose(); + return {displayName: 'ts-esm-async-function-config', verbose}; + }; + `, + 'package.json': '{"type": "module"}', + }); + + const {configs, globalConfig} = getConfig(path.join(DIR)); + + expect(configs).toHaveLength(1); + expect(configs[0].displayName?.name).toBe('ts-esm-async-function-config'); + expect(globalConfig.verbose).toBe(true); + }); + + test('throws if type errors are encountered when package.json#type=module', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {testTimeout: '10000'}; + export default config; + `, + 'package.json': '{"type": "module"}', + }); + + const {stderr, exitCode} = runJest(DIR); + + expect(stderr).toMatch( + "jest.config.ts(2,25): error TS2322: Type 'string' is not assignable to type 'number'.", + ); + expect(exitCode).toBe(1); + }); + + test('throws if syntax errors are encountered when package.json#type=module', () => { + writeFiles(DIR, { + '__tests__/dummy.test.js': + "test('dummy', () => expect(123).toBe(123));", + 'jest.config.ts': ` + import type {Config} from 'jest'; + const config: Config = {verbose: true}; + export default get config; + `, + 'package.json': '{}', + }); + + const {stderr, exitCode} = runJest(DIR); + + expect(stderr).toMatch( + "jest.config.ts(3,16): error TS2304: Cannot find name 'get'.", + ); + expect(exitCode).toBe(1); + }); + }); +}); diff --git a/jest.config.ci.mjs b/jest.config.ci.mjs index ffba96d753c6..2b2f36983f5e 100644 --- a/jest.config.ci.mjs +++ b/jest.config.ci.mjs @@ -5,10 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import jestConfigBase from './jest.config.mjs'; +import baseConfig from './jest.config.mjs'; +/** @type {import('jest').Config} */ export default { - ...jestConfigBase, + ...baseConfig, coverageReporters: ['json'], reporters: [ 'github-actions', diff --git a/jest.config.mjs b/jest.config.mjs index 1ba1f96f823e..28c68e7890e4 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -8,7 +8,7 @@ import {createRequire} from 'module'; const require = createRequire(import.meta.url); -/** @type import('@jest/types').Config.InitialOptions */ +/** @type {import('jest').Config} */ export default { collectCoverageFrom: [ '**/packages/*/**/*.js', diff --git a/jest.config.ts.mjs b/jest.config.ts.mjs index 890857922fe9..d58bd155e20e 100644 --- a/jest.config.ts.mjs +++ b/jest.config.ts.mjs @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import jestConfigBase from './jest.config.mjs'; +import baseConfig from './jest.config.mjs'; +/** @type {import('jest').Config} */ export default { projects: [ { @@ -14,7 +15,7 @@ export default { color: 'blue', name: 'ts-integration', }, - modulePathIgnorePatterns: jestConfigBase.modulePathIgnorePatterns, + modulePathIgnorePatterns: baseConfig.modulePathIgnorePatterns, roots: ['/e2e/__tests__'], testMatch: ['/e2e/__tests__/ts*'], }, @@ -23,7 +24,7 @@ export default { color: 'blue', name: 'type-tests', }, - modulePathIgnorePatterns: jestConfigBase.modulePathIgnorePatterns, + modulePathIgnorePatterns: baseConfig.modulePathIgnorePatterns, roots: ['/packages'], runner: 'jest-runner-tsd', testMatch: ['**/__typetests__/**/*.ts'], diff --git a/packages/jest/__typetests__/jest.test.ts b/packages/jest/__typetests__/jest.test.ts new file mode 100644 index 000000000000..82ed8518fe3e --- /dev/null +++ b/packages/jest/__typetests__/jest.test.ts @@ -0,0 +1,14 @@ +/** + * 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 {expectType} from 'tsd-lite'; +import type {Config as ConfigTypes} from '@jest/types'; +import type {Config} from 'jest'; + +declare const config: Config; + +expectType(config); diff --git a/packages/jest/__typetests__/tsconfig.json b/packages/jest/__typetests__/tsconfig.json new file mode 100644 index 000000000000..165ba1343021 --- /dev/null +++ b/packages/jest/__typetests__/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "skipLibCheck": true, + + "types": [] + }, + "include": ["./**/*"] +} diff --git a/packages/jest/package.json b/packages/jest/package.json index 0d7b32ecd020..7538b58966a0 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -14,9 +14,14 @@ }, "dependencies": { "@jest/core": "^28.1.0", + "@jest/types": "^28.1.0", "import-local": "^3.0.2", "jest-cli": "^28.1.0" }, + "devDependencies": { + "@tsd/typescript": "~4.6.2", + "tsd-lite": "^0.5.1" + }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, diff --git a/packages/jest/src/index.ts b/packages/jest/src/index.ts index d101fd921a01..415c7a7ffa47 100644 --- a/packages/jest/src/index.ts +++ b/packages/jest/src/index.ts @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +import type {Config as ConfigTypes} from '@jest/types'; + export { SearchSource, createTestScheduler, @@ -13,3 +15,5 @@ export { } from '@jest/core'; export {run} from 'jest-cli'; + +export type Config = ConfigTypes.InitialOptions; diff --git a/packages/jest/tsconfig.json b/packages/jest/tsconfig.json index 21169670d305..454abee9f0e8 100644 --- a/packages/jest/tsconfig.json +++ b/packages/jest/tsconfig.json @@ -5,5 +5,9 @@ "outDir": "build" }, "include": ["./src/**/*"], - "references": [{"path": "../jest-cli"}, {"path": "../jest-core"}] + "references": [ + {"path": "../jest-cli"}, + {"path": "../jest-core"}, + {"path": "../jest-types"} + ] } diff --git a/yarn.lock b/yarn.lock index 050470a7b40c..3c584bce388d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13777,8 +13777,11 @@ __metadata: resolution: "jest@workspace:packages/jest" dependencies: "@jest/core": ^28.1.0 + "@jest/types": ^28.1.0 + "@tsd/typescript": ~4.6.2 import-local: ^3.0.2 jest-cli: ^28.1.0 + tsd-lite: ^0.5.1 peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: