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
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
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
308 changes: 308 additions & 0 deletions e2e/__tests__/tsIntegration.test.ts
Expand Up @@ -54,6 +54,45 @@ describe('when `Config` type is imported from "@jest/types"', () => {
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': `
import type {Config} from '@jest/types';
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': `
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-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 @@ describe('when `Config` type is imported from "@jest/types"', () => {
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': `
import type {Config} from '@jest/types';
const config: Config.InitialOptions = {testTimeout: '10000'};
export default config;
`,
'package.json': '{}',
});

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

expect(stderr).toMatch(
"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': `
import type {Config} from '@jest/types';
const config: Config.InitialOptions = {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 All @@ -114,6 +191,45 @@ describe('when `Config` type is imported from "@jest/types"', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.ts': `
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': `
import type {Config} from '@jest/types';
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': `
import type {Config} from '@jest/types';
async function getVerbose() {return true;}
export default async (): Promise<Config.InitialOptions> => {
Expand Down Expand Up @@ -168,6 +284,44 @@ describe('when `Config` type is imported from "@jest/types"', () => {
);
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': `
import type {Config} from '@jest/types';
const config: Config.InitialOptions = {testTimeout: '10000'};
export default config;
`,
'package.json': '{"type": "module"}',
});

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

expect(stderr).toMatch(
"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': `
import type {Config} from '@jest/types';
const config: Config.InitialOptions = {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);
});
});

describe('when `Config` type is imported from "jest"', () => {
Expand Down Expand Up @@ -210,6 +364,45 @@ describe('when `Config` type is imported from "jest"', () => {
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': `
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 CTS file', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));",
'jest.config.cts': `
import type {Config} from 'jest';
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 @@ describe('when `Config` type is imported from "jest"', () => {
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': `
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.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': `
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.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 @@ describe('when `Config` type is imported from "jest"', () => {
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': `
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 CTS file when package.json#type=module', () => {
writeFiles(DIR, {
'__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));",
'jest.config.cts': `
import type {Config} from 'jest';
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 @@ describe('when `Config` type is imported from "jest"', () => {
);
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': `
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.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': `
import type {Config} from 'jest';
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);
});
});
@@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export default {};
@@ -0,0 +1 @@
{}