From faa4fffad84d3ca3689cddb2e90653f54c167a60 Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Thu, 13 Apr 2023 12:35:09 +0200 Subject: [PATCH 1/9] Allow for jest.config.cts, add fixture and snapshots --- packages/jest-cli/src/__tests__/args.test.ts | 4 +- .../has-jest-config-file-cts/jest.config.cts | 8 +++ .../has-jest-config-file-cts/package.json | 1 + .../__tests__/__snapshots__/init.test.ts.snap | 70 +++++++++++++++++++ packages/jest-config/src/constants.ts | 2 + .../src/readConfigFileAndSetRootDir.ts | 3 +- 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts create mode 100644 packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json diff --git a/packages/jest-cli/src/__tests__/args.test.ts b/packages/jest-cli/src/__tests__/args.test.ts index 512472ac83ed..00e36a329aff 100644 --- a/packages/jest-cli/src/__tests__/args.test.ts +++ b/packages/jest-cli/src/__tests__/args.test.ts @@ -89,13 +89,13 @@ describe('check', () => { it('raises an exception if config is not a valid JSON string', () => { expect(() => check(argv({config: 'x:1'}))).toThrow( - 'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .json', + 'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .cts, .json', ); }); it('raises an exception if config is not a supported file type', () => { const message = - 'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .json'; + 'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .cts, .json'; expect(() => check(argv({config: 'jest.configjs'}))).toThrow(message); expect(() => check(argv({config: 'jest.config.exe'}))).toThrow(message); diff --git a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts new file mode 100644 index 000000000000..4f69b4e3bda0 --- /dev/null +++ b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts @@ -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 {}; diff --git a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap index 31cca289857e..321310fad460 100644 --- a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap +++ b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap @@ -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, @@ -54,6 +63,67 @@ Object { } `; +exports[`init project using jest.config.cts ask the user whether he wants to use Typescript or not user answered with "Yes" 1`] = ` +Array [ + Object { + "initial": true, + "message": "Would you like to use Jest when running "test" script in "package.json"?", + "name": "scripts", + "type": "confirm", + }, + Object { + "initial": false, + "message": "Would you like to use Typescript for the configuration file?", + "name": "useTypescript", + "type": "confirm", + }, + Object { + "choices": Array [ + Object { + "title": "node", + "value": "node", + }, + Object { + "title": "jsdom (browser-like)", + "value": "jsdom", + }, + ], + "initial": 0, + "message": "Choose the test environment that will be used for testing", + "name": "environment", + "type": "select", + }, + Object { + "initial": false, + "message": "Do you want Jest to add coverage reports?", + "name": "coverage", + "type": "confirm", + }, + Object { + "choices": Array [ + Object { + "title": "v8", + "value": "v8", + }, + Object { + "title": "babel", + "value": "babel", + }, + ], + "initial": 0, + "message": "Which provider should be used to instrument code for coverage?", + "name": "coverageProvider", + "type": "select", + }, + Object { + "initial": false, + "message": "Automatically clear mock calls, instances, contexts and results before every test?", + "name": "clearMocks", + "type": "confirm", + }, +] +`; + exports[`init project using jest.config.ts ask the user whether he wants to use Typescript or not user answered with "Yes" 1`] = ` Array [ Object { diff --git a/packages/jest-config/src/constants.ts b/packages/jest-config/src/constants.ts index 476874874ec7..63172c139250 100644 --- a/packages/jest-config/src/constants.ts +++ b/packages/jest-config/src/constants.ts @@ -15,11 +15,13 @@ export const JEST_CONFIG_EXT_CJS = '.cjs'; export const JEST_CONFIG_EXT_MJS = '.mjs'; export const JEST_CONFIG_EXT_JS = '.js'; export const JEST_CONFIG_EXT_TS = '.ts'; +export const JEST_CONFIG_EXT_CTS = '.cts'; export const JEST_CONFIG_EXT_JSON = '.json'; export const JEST_CONFIG_EXT_ORDER = Object.freeze([ JEST_CONFIG_EXT_JS, JEST_CONFIG_EXT_TS, JEST_CONFIG_EXT_MJS, JEST_CONFIG_EXT_CJS, + JEST_CONFIG_EXT_CTS, JEST_CONFIG_EXT_JSON, ]); diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index fef76c2c7ab6..48e2c3e0d0a2 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -13,6 +13,7 @@ import type {Service} from 'ts-node'; import type {Config} from '@jest/types'; import {interopRequireDefault, requireOrImportModule} from 'jest-util'; import { + JEST_CONFIG_EXT_CTS, JEST_CONFIG_EXT_JSON, JEST_CONFIG_EXT_TS, PACKAGE_JSON, @@ -26,7 +27,7 @@ import { export default async function readConfigFileAndSetRootDir( configPath: string, ): Promise { - const isTS = configPath.endsWith(JEST_CONFIG_EXT_TS); + const isTS = configPath.endsWith(JEST_CONFIG_EXT_TS || JEST_CONFIG_EXT_CTS); const isJSON = configPath.endsWith(JEST_CONFIG_EXT_JSON); let configObject; From 2e0088b488f766d609f48ab79ee71430a379bd9a Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Thu, 13 Apr 2023 13:16:54 +0200 Subject: [PATCH 2/9] Remove unnecessary snapshot --- .../__tests__/__snapshots__/init.test.ts.snap | 62 +------------------ 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap index 321310fad460..64f0e273f2f8 100644 --- a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap +++ b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap @@ -63,67 +63,6 @@ Object { } `; -exports[`init project using jest.config.cts ask the user whether he wants to use Typescript or not user answered with "Yes" 1`] = ` -Array [ - Object { - "initial": true, - "message": "Would you like to use Jest when running "test" script in "package.json"?", - "name": "scripts", - "type": "confirm", - }, - Object { - "initial": false, - "message": "Would you like to use Typescript for the configuration file?", - "name": "useTypescript", - "type": "confirm", - }, - Object { - "choices": Array [ - Object { - "title": "node", - "value": "node", - }, - Object { - "title": "jsdom (browser-like)", - "value": "jsdom", - }, - ], - "initial": 0, - "message": "Choose the test environment that will be used for testing", - "name": "environment", - "type": "select", - }, - Object { - "initial": false, - "message": "Do you want Jest to add coverage reports?", - "name": "coverage", - "type": "confirm", - }, - Object { - "choices": Array [ - Object { - "title": "v8", - "value": "v8", - }, - Object { - "title": "babel", - "value": "babel", - }, - ], - "initial": 0, - "message": "Which provider should be used to instrument code for coverage?", - "name": "coverageProvider", - "type": "select", - }, - Object { - "initial": false, - "message": "Automatically clear mock calls, instances, contexts and results before every test?", - "name": "clearMocks", - "type": "confirm", - }, -] -`; - exports[`init project using jest.config.ts ask the user whether he wants to use Typescript or not user answered with "Yes" 1`] = ` Array [ Object { @@ -270,6 +209,7 @@ module.exports = { // "cjs", // "jsx", // "ts", + // "cts", // "tsx", // "json", // "node" From 19ade2976a65c7723d2304930944ec7552e78a5a Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Thu, 13 Apr 2023 14:22:30 +0200 Subject: [PATCH 3/9] Remove single line in array of file extensions --- .../jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap index 64f0e273f2f8..85d4439ba92e 100644 --- a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap +++ b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.ts.snap @@ -209,7 +209,6 @@ module.exports = { // "cjs", // "jsx", // "ts", - // "cts", // "tsx", // "json", // "node" From a5690d3e5172eaa40f3c4b9bbbc0fb3e94ef12ab Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Mon, 17 Apr 2023 17:34:26 +0200 Subject: [PATCH 4/9] Add changelog entry, docs --- CHANGELOG.md | 1 + docs/Configuration.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b63187776f8..ce77e3139006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - `[jest-mock]` Tweak typings to allow `jest.replaceProperty()` replace methods ([#14008](https://github.com/facebook/jest/pull/14008)) - `[jest-snapshot]` Fix a potential bug when not using prettier and improve performance ([#14036](https://github.com/facebook/jest/pull/14036)) - `[@jest/transform]` Do not instrument `.json` modules ([#14048](https://github.com/facebook/jest/pull/14048)) +- `[jest-config]` Allow loading `jest.config.cts` files ([#14070](https://github.com/facebook/jest/pull/14070)) ### Chore & Maintenance diff --git a/docs/Configuration.md b/docs/Configuration.md index ed7eb7c09f00..c9691beb820e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -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 From 80bbbf6af88ff61d828b223022b00838366c61c2 Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Mon, 17 Apr 2023 17:34:49 +0200 Subject: [PATCH 5/9] Add integration test for .cts config files --- e2e/__tests__/tsIntegration.test.ts | 308 ++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) diff --git a/e2e/__tests__/tsIntegration.test.ts b/e2e/__tests__/tsIntegration.test.ts index 32d9f01ccd66..57c79a9a782c 100644 --- a/e2e/__tests__/tsIntegration.test.ts +++ b/e2e/__tests__/tsIntegration.test.ts @@ -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 => { + 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));", @@ -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));", @@ -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 => { + 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 => { @@ -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"', () => { @@ -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 => { + 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));", @@ -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));", @@ -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 => { + 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));", @@ -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); + }); }); From bf0e3771d867ffa41bb060f6da50fd434609e610 Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Sat, 29 Apr 2023 17:30:34 +0200 Subject: [PATCH 6/9] Change type import for .cts files in integration tests --- CHANGELOG.md | 2 +- e2e/__tests__/tsIntegration.test.ts | 32 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce77e3139006..01733a7dadc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,12 @@ ### Fixes - `[jest-config]` Handle frozen config object ([#14054](https://github.com/facebook/jest/pull/14054)) +- `[jest-config]` Allow loading `jest.config.cts` files ([#14070](https://github.com/facebook/jest/pull/14070)) - `[jest-environment-jsdom, jest-environment-node]` Fix assignment of `customExportConditions` via `testEnvironmentOptions` when custom env subclass defines a default value ([#13989](https://github.com/facebook/jest/pull/13989)) - `[jest-matcher-utils]` Fix copying value of inherited getters ([#14007](https://github.com/facebook/jest/pull/14007)) - `[jest-mock]` Tweak typings to allow `jest.replaceProperty()` replace methods ([#14008](https://github.com/facebook/jest/pull/14008)) - `[jest-snapshot]` Fix a potential bug when not using prettier and improve performance ([#14036](https://github.com/facebook/jest/pull/14036)) - `[@jest/transform]` Do not instrument `.json` modules ([#14048](https://github.com/facebook/jest/pull/14048)) -- `[jest-config]` Allow loading `jest.config.cts` files ([#14070](https://github.com/facebook/jest/pull/14070)) ### Chore & Maintenance diff --git a/e2e/__tests__/tsIntegration.test.ts b/e2e/__tests__/tsIntegration.test.ts index 57c79a9a782c..4c35623badf7 100644 --- a/e2e/__tests__/tsIntegration.test.ts +++ b/e2e/__tests__/tsIntegration.test.ts @@ -58,7 +58,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {displayName: 'ts-object-config', verbose: true}; export default config; `, @@ -76,7 +76,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config.InitialOptions['verbose'] = await getVerbose(); @@ -135,7 +135,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {testTimeout: '10000'}; export default config; `, @@ -154,7 +154,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {verbose: true}; export default get config; `, @@ -212,7 +212,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {displayName: 'ts-esm-object-config', verbose: true}; export default config; `, @@ -230,7 +230,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config.InitialOptions['verbose'] = await getVerbose(); @@ -289,7 +289,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {testTimeout: '10000'}; export default config; `, @@ -308,7 +308,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from '@jest/types'; + /** @type {import('@jest/types').Config} */ const config: Config.InitialOptions = {verbose: true}; export default get config; `, @@ -368,7 +368,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {displayName: 'ts-object-config', verbose: true}; export default config; `, @@ -386,7 +386,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config['verbose'] = await getVerbose(); @@ -445,7 +445,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {testTimeout: '10000'}; export default config; `, @@ -464,7 +464,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {verbose: true}; export default get config; `, @@ -522,7 +522,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {displayName: 'ts-esm-object-config', verbose: true}; export default config; `, @@ -540,7 +540,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config['verbose'] = await getVerbose(); @@ -599,7 +599,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {testTimeout: '10000'}; export default config; `, @@ -618,7 +618,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - import type {Config} from 'jest'; + /** @type {import('@jest/types').Config} */ const config: Config = {verbose: true}; export default get config; `, From f528d35a9b88bf37ab5f3cb2e13fcb3dc7cb0d5e Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Thu, 4 Jan 2024 16:51:50 +0100 Subject: [PATCH 7/9] implement suggested change, fix tests --- e2e/__tests__/tsIntegration.test.ts | 36 +++++++++---------- .../src/readConfigFileAndSetRootDir.ts | 4 ++- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/e2e/__tests__/tsIntegration.test.ts b/e2e/__tests__/tsIntegration.test.ts index 4c35623badf7..fcb4f2df10af 100644 --- a/e2e/__tests__/tsIntegration.test.ts +++ b/e2e/__tests__/tsIntegration.test.ts @@ -58,9 +58,9 @@ describe('when `Config` type is imported from "@jest/types"', () => { 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; + import type {Config} from '@jest/types'; + const config: Config.InitialOptions = {displayName: 'ts-object-config', verbose: true}; + export default config; `, 'package.json': '{}', }); @@ -76,7 +76,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config.InitialOptions['verbose'] = await getVerbose(); @@ -135,7 +135,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; const config: Config.InitialOptions = {testTimeout: '10000'}; export default config; `, @@ -154,7 +154,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; const config: Config.InitialOptions = {verbose: true}; export default get config; `, @@ -212,7 +212,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; const config: Config.InitialOptions = {displayName: 'ts-esm-object-config', verbose: true}; export default config; `, @@ -230,7 +230,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config.InitialOptions['verbose'] = await getVerbose(); @@ -289,7 +289,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; const config: Config.InitialOptions = {testTimeout: '10000'}; export default config; `, @@ -308,7 +308,7 @@ describe('when `Config` type is imported from "@jest/types"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from '@jest/types'; const config: Config.InitialOptions = {verbose: true}; export default get config; `, @@ -368,7 +368,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {displayName: 'ts-object-config', verbose: true}; export default config; `, @@ -386,7 +386,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config['verbose'] = await getVerbose(); @@ -445,7 +445,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {testTimeout: '10000'}; export default config; `, @@ -464,7 +464,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {verbose: true}; export default get config; `, @@ -522,7 +522,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {displayName: 'ts-esm-object-config', verbose: true}; export default config; `, @@ -540,7 +540,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; async function getVerbose() {return true;} export default async (): Promise => { const verbose: Config['verbose'] = await getVerbose(); @@ -599,7 +599,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(12).toBe(12));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {testTimeout: '10000'}; export default config; `, @@ -618,7 +618,7 @@ describe('when `Config` type is imported from "jest"', () => { writeFiles(DIR, { '__tests__/dummy.test.js': "test('dummy', () => expect(123).toBe(123));", 'jest.config.cts': ` - /** @type {import('@jest/types').Config} */ + import type {Config} from 'jest'; const config: Config = {verbose: true}; export default get config; `, diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index 9e618b22d794..84ec03e6ccc9 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -27,7 +27,9 @@ import { export default async function readConfigFileAndSetRootDir( configPath: string, ): Promise { - const isTS = configPath.endsWith(JEST_CONFIG_EXT_TS || JEST_CONFIG_EXT_CTS); + const isTS = + configPath.endsWith(JEST_CONFIG_EXT_TS) || + configPath.endsWith(JEST_CONFIG_EXT_CTS); const isJSON = configPath.endsWith(JEST_CONFIG_EXT_JSON); let configObject; From a8cdf6d96d64020fc1ea0a7f08e696eb24917982 Mon Sep 17 00:00:00 2001 From: Timon Jurschitsch Date: Thu, 4 Jan 2024 17:48:10 +0100 Subject: [PATCH 8/9] add missing files in test fixtures --- .../__fixtures__/has-jest-config-file-cts/jest.config.cts | 8 ++++++++ .../__fixtures__/has-jest-config-file-cts/package.json | 1 + 2 files changed, 9 insertions(+) create mode 100644 packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts create mode 100644 packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/package.json diff --git a/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts b/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts new file mode 100644 index 000000000000..4f69b4e3bda0 --- /dev/null +++ b/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts @@ -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 {}; diff --git a/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/package.json b/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/create-jest/src/__tests__/__fixtures__/has-jest-config-file-cts/package.json @@ -0,0 +1 @@ +{} From a71fab18b1aa037cf4b7865320294d84f808519b Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 5 Jan 2024 08:10:10 +0100 Subject: [PATCH 9/9] remove unused files --- .../__fixtures__/has-jest-config-file-cts/jest.config.cts | 8 -------- .../__fixtures__/has-jest-config-file-cts/package.json | 1 - 2 files changed, 9 deletions(-) delete mode 100644 packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts delete mode 100644 packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json diff --git a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts deleted file mode 100644 index 4f69b4e3bda0..000000000000 --- a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/jest.config.cts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * 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 {}; diff --git a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json b/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json deleted file mode 100644 index 0967ef424bce..000000000000 --- a/packages/jest-cli/src/init/__tests__/__fixtures__/has-jest-config-file-cts/package.json +++ /dev/null @@ -1 +0,0 @@ -{}