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

Allow jest.config.cts #14070

Merged
merged 12 commits into from
Jan 5, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `[jest-config]` [**BREAKING**] Add `mts` and `cts` to default `moduleFileExtensions` config ([#14369](https://github.com/facebook/jest/pull/14369))
- `[jest-config]` [**BREAKING**] Update `testMatch` and `testRegex` default option for supporting `mjs`, `cjs`, `mts`, and `cts` ([#14584](https://github.com/jestjs/jest/pull/14584))
- `[jest-config]` Loads config file from provided path in `package.json` ([#14044](https://github.com/facebook/jest/pull/14044))
- `[jest-config]` Allow loading `jest.config.cts` files ([#14070](https://github.com/facebook/jest/pull/14070))
- `[@jest/core]` Group together open handles with the same stack trace ([#13417](https://github.com/jestjs/jest/pull/13417), & [#14789](https://github.com/jestjs/jest/pull/14789))
- `[@jest/core]` Add `perfStats` to surface test setup overhead ([#14622](https://github.com/jestjs/jest/pull/14622))
- `[@jest/core]` [**BREAKING**] Changed `--filter` to accept an object with shape `{ filtered: Array<string> }` to match [documentation](https://jestjs.io/docs/cli#--filterfile) ([#13319](https://github.com/jestjs/jest/pull/13319))
Expand Down
2 changes: 1 addition & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: Configuring Jest

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|mjs|cjs|json`. You can use [`--config`](CLI.md#--configpath) flag to pass an explicit path to the file.
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|cts|json`. You can use [`--config`](CLI.md#--configpath) flag to pass an explicit path to the file.

:::note

Expand Down
310 changes: 309 additions & 1 deletion e2e/__tests__/tsIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,45 @@
expect(globalConfig.verbose).toBe(true);
});

test('with object config exported from CTS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {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 CTS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
async function getVerbose() {return true;}
export default async (): Promise<Config.InitialOptions> => {
const verbose: Config.InitialOptions['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));",
Expand Down Expand Up @@ -92,6 +131,44 @@
expect(exitCode).toBe(1);
});

test('throws if type errors are encountered with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {testTimeout: '10000'};
export default config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(

Check failure on line 147 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if type errors are encountered with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(2,40): error TS2322: Type 'string' is not assignable to type 'number'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {testTimeout: '10000'}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:147:20)

Check failure on line 147 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if type errors are encountered with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(2,40): error TS2322: Type 'string' is not assignable to type 'number'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {testTimeout: '10000'}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:147:20)
"jest.config.cts(2,40): error TS2322: Type 'string' is not assignable to type 'number'.",
);
expect(exitCode).toBe(1);
});

test('throws if syntax errors are encountered with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {verbose: true};
export default get config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(

Check failure on line 166 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if syntax errors are encountered with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(3,16): error TS2304: Cannot find name 'get'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {verbose: true}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:166:20)

Check failure on line 166 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if syntax errors are encountered with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(3,16): error TS2304: Cannot find name 'get'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {verbose: true}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:166:20)
"jest.config.cts(3,16): error TS2304: Cannot find name 'get'.",
);
expect(exitCode).toBe(1);
});

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));",
Expand All @@ -114,7 +191,46 @@
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.ts': `
import type {Config} from '@jest/types';
import type {Config} from '@jest/types';
async function getVerbose() {return true;}
export default async (): Promise<Config.InitialOptions> => {
const verbose: Config.InitialOptions['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('works with object config exported from CTS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {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 CTS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
async function getVerbose() {return true;}
export default async (): Promise<Config.InitialOptions> => {
const verbose: Config.InitialOptions['verbose'] = await getVerbose();
Expand Down Expand Up @@ -168,6 +284,44 @@
);
expect(exitCode).toBe(1);
});

test('throws if type errors are encountered when package.json#type=module with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {testTimeout: '10000'};
export default config;
`,
'package.json': '{"type": "module"}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(

Check failure on line 301 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if type errors are encountered when package.json#type=module with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(2,40): error TS2322: Type 'string' is not assignable to type 'number'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {testTimeout: '10000'}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:301:20)

Check failure on line 301 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if type errors are encountered when package.json#type=module with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(2,40): error TS2322: Type 'string' is not assignable to type 'number'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {testTimeout: '10000'}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:301:20)
"jest.config.cts(2,40): 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 with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config.InitialOptions = {verbose: true};
export default get config;
`,
'package.json': '{"type": "module"}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(

Check failure on line 320 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if syntax errors are encountered when package.json#type=module with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(3,16): error TS2304: Cannot find name 'get'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {verbose: true}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:320:20)

Check failure on line 320 in e2e/__tests__/tsIntegration.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Compatibility

when `Config` type is imported from "@jest/types" › throws if syntax errors are encountered when package.json#type=module with CTS config

expect(received).toMatch(expected) Expected substring: "jest.config.cts(3,16): error TS2304: Cannot find name 'get'." Received string: "/home/runner/work/jest/jest/e2e/ts-node-integration/jest.config.cts:2 const config: Config.InitialOptions = {verbose: true}; ^^^^^^· SyntaxError: Missing initializer in const declaration at requireOrImportModule (packages/jest-util/build/index.js:881:28) at Object.toMatch (e2e/__tests__/tsIntegration.test.ts:320:20)
"jest.config.cts(3,16): error TS2304: Cannot find name 'get'.",
);
expect(exitCode).toBe(1);
});
});

describe('when `Config` type is imported from "jest"', () => {
Expand Down Expand Up @@ -210,6 +364,45 @@
expect(globalConfig.verbose).toBe(true);
});

test('with object config exported from CTS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
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 CTS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
async function getVerbose() {return true;}
export default async (): Promise<Config> => {
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));",
Expand Down Expand Up @@ -248,6 +441,44 @@
expect(exitCode).toBe(1);
});

test('throws if type errors are encountered with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config = {testTimeout: '10000'};
export default config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.cts(2,25): error TS2322: Type 'string' is not assignable to type 'number'.",
);
expect(exitCode).toBe(1);
});

test('throws if syntax errors are encountered with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config = {verbose: true};
export default get config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.cts(3,16): error TS2304: Cannot find name 'get'.",
);
expect(exitCode).toBe(1);
});

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));",
Expand Down Expand Up @@ -287,6 +518,45 @@
expect(globalConfig.verbose).toBe(true);
});

test('works with object config exported from CTS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
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 CTS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
async function getVerbose() {return true;}
export default async (): Promise<Config> => {
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));",
Expand Down Expand Up @@ -324,4 +594,42 @@
);
expect(exitCode).toBe(1);
});

test('throws if type errors are encountered when package.json#type=module with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config = {testTimeout: '10000'};
export default config;
`,
'package.json': '{"type": "module"}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.cts(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 with CTS config', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
/** @type {import('@jest/types').Config} */
const config: Config = {verbose: true};
export default get config;
`,
'package.json': '{"type": "module"}',
});

const {stderr, exitCode} = runJest(DIR);

expect(stderr).toMatch(
"jest.config.cts(3,16): error TS2304: Cannot find name 'get'.",
);
expect(exitCode).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ Object {
}
`;

exports[`init has-jest-config-file-cts 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,
Expand Down
Loading
Loading