Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-config):Support loading TS config files via docblock loader #15190

Merged
merged 17 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- `[jest-config]` Allow loading `jest.config.cts` files ([#14070](https://github.com/facebook/jest/pull/14070))
- `[jest-config]` Added an option to disable `ts-node` typechecking ([#15161](https://github.com/jestjs/jest/pull/15161))
- `[jest-config]` Show `rootDir` in error message when a `preset` fails to load ([#15194](https://github.com/jestjs/jest/pull/15194))
- `[jest-config]` Support loading TS config files using `esbuild-register` via docblock loader ([#15190](https://github.com/jestjs/jest/pull/15190))
- `[@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
18 changes: 16 additions & 2 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,23 @@ export default async (): Promise<Config> => {

:::tip

To read TypeScript configuration files Jest requires [`ts-node`](https://npmjs.com/package/ts-node). Make sure it is installed in your project.
To read TypeScript configuration files Jest by default requires [`ts-node`](https://npmjs.com/package/ts-node). You can override this behavior by adding a `@jest-config-loader` docblock at the top of the file. Currently, [`ts-node`](https://npmjs.com/package/ts-node) and [`esbuild-register`](https://npmjs.com/package/esbuild-register) is supported. Make sure `ts-node` or the loader you specify is installed.

To read configuration files without typechecking, You can set `JEST_CONFIG_TRANSPILE_ONLY` environment variable to `true` (case insensitive).
```ts title="jest.config.ts"
/** @jest-config-loader ts-node */
// or
/** @jest-config-loader esbuild-register */

import type {Config} from 'jest';

const config: Config = {
verbose: true,
};

export default config;
```

If you are using `ts-node`, you can set `JEST_CONFIG_TRANSPILE_ONLY` environment variable to `true` (case insensitive) to read configuration files without typechecking.
Copy link
Member

Choose a reason for hiding this comment

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

not for this PR, but maybe in a follow-up - should we drop this new env variable and use docblock config instead (like we support for test environment: https://jestjs.io/blog/2022/04/25/jest-28#inline-testenvironmentoptions)? Then people could also e.g. opt into using swc (https://typestrong.org/ts-node/docs/swc/)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would be better, will send PR for that
we might also need to add support for swc based loader at some point #12156


:::

Expand Down
13 changes: 13 additions & 0 deletions e2e/__tests__/multipleConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,16 @@ test('multiple configs error can be suppressed by using --config', () => {
);
expect(exitCode).toBe(0);
});

test('should works correctly when using different loaders in different projects', () => {
const {exitCode, stdout, stderr} = runJest(
'multi-project-multiple-configs',
['--projects', 'prj-1', 'prj-2'],
{
skipPkgJsonCheck: true,
},
);
expect(exitCode).toBe(0);
console.log(stdout);
console.log(stderr);
});
85 changes: 31 additions & 54 deletions e2e/__tests__/readInitialOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,60 +46,27 @@ describe('readInitialOptions', () => {
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a jest.config.js file', async () => {
const configFile = resolveFixture('js-config', 'jest.config.js');
const rootDir = resolveFixture('js-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a package.json file', async () => {
const configFile = resolveFixture('pkg-config', 'package.json');
const rootDir = resolveFixture('pkg-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'package.json', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a jest.config.ts file', async () => {
const configFile = resolveFixture('ts-config', 'jest.config.ts');
const rootDir = resolveFixture('ts-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'jest.config.ts', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a jest.config.mjs file', async () => {
const configFile = resolveFixture('mjs-config', 'jest.config.mjs');
const rootDir = resolveFixture('mjs-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'jest.config.mjs', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a jest.config.json file', async () => {
const configFile = resolveFixture('json-config', 'jest.config.json');
const rootDir = resolveFixture('json-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'jest.config.json', rootDir});
expect(configPath).toEqual(configFile);
});
test('should read a jest config exporting an async function', async () => {
const configFile = resolveFixture('async-config', 'jest.config.js');
const rootDir = resolveFixture('async-config');
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: 'async-config', rootDir});
expect(configPath).toEqual(configFile);
});

test.each([
SimenB marked this conversation as resolved.
Show resolved Hide resolved
['js-config', 'jest.config.js', 'jest.config.js'],
['pkg-config', 'package.json', 'package.json'],
['ts-node-config', 'jest.config.ts', 'jest.config.ts'],
['ts-esbuild-register-config', 'jest.config.ts', 'jest.config.ts'],
['mjs-config', 'jest.config.mjs', 'jest.config.mjs'],
['json-config', 'jest.config.json', 'jest.config.json'],
['async-config', 'jest.config.js', 'async-config'],
])(
'should read %s/%s file',
async (directory: string, filename: string, configString: string) => {
const configFile = resolveFixture(directory, filename);
const rootDir = resolveFixture(directory);
const {config, configPath} = await proxyReadInitialOptions(undefined, {
cwd: rootDir,
});
expect(config).toEqual({jestConfig: configString, rootDir});
expect(configPath).toEqual(configFile);
},
);

test('should be able to skip config reading, instead read from cwd', async () => {
const expectedConfigFile = resolveFixture(
Expand All @@ -121,6 +88,16 @@ describe('readInitialOptions', () => {
expect(configPath).toEqual(expectedConfigFile);
});

test('should give an error when using unsupported loader', async () => {
const cwd = resolveFixture('ts-loader-config');
const error: Error = await proxyReadInitialOptions(undefined, {cwd}).catch(
error => error,
);
expect(error.message).toContain(
"Jest: 'ts-loader' is not a valid TypeScript configuration loader.",
);
});

test('should give an error when there are multiple config files', async () => {
const cwd = resolveFixture('multiple-config-files');
const error: Error = await proxyReadInitialOptions(undefined, {cwd}).catch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export default {
jestConfig: 'jest.config.ts',
};

test('dummy test', () => {
expect(1).toBe(1);
});
16 changes: 16 additions & 0 deletions e2e/multi-project-multiple-configs/prj-1/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.
*
* @jest-config-loader esbuild-register
*/

import type {Config} from 'jest';

const config: Config = {
displayName: 'PROJECT 1',
};

export default config;
10 changes: 10 additions & 0 deletions e2e/multi-project-multiple-configs/prj-2/__tests__/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* 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.
*/

test('dummy test', () => {
expect(2).toBe(2);
});
16 changes: 16 additions & 0 deletions e2e/multi-project-multiple-configs/prj-2/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.
*
* @jest-config-loader ts-node
*/

import type {Config} from 'jest';

const config: Config = {
displayName: 'PROJECT 2',
};

export default config;
16 changes: 16 additions & 0 deletions e2e/read-initial-options/ts-esbuild-register-config/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.
*
* @jest-config-loader esbuild-register
*/

interface Config {
jestConfig: string;
}

export default {
jestConfig: 'jest.config.ts',
} as Config;
16 changes: 16 additions & 0 deletions e2e/read-initial-options/ts-loader-config/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.
*
* @jest-config-loader ts-loader
*/

interface Config {
jestConfig: string;
}

export default {
jestConfig: 'jest.config.ts',
} as Config;
16 changes: 16 additions & 0 deletions e2e/read-initial-options/ts-node-config/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.
*
* @jest-config-loader ts-node
*/

interface Config {
jestConfig: string;
}

export default {
jestConfig: 'jest.config.ts',
} as Config;
7 changes: 7 additions & 0 deletions packages/jest-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@
},
"peerDependencies": {
"@types/node": "*",
"esbuild-register": ">=3.4.0",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"esbuild-register": {
"optional": true
},
"ts-node": {
"optional": true
}
Expand All @@ -42,6 +46,7 @@
"glob": "^10.3.10",
"graceful-fs": "^4.2.9",
"jest-circus": "workspace:*",
"jest-docblock": "workspace:*",
"jest-environment-node": "workspace:*",
"jest-get-type": "workspace:*",
"jest-regex-util": "workspace:*",
Expand All @@ -59,6 +64,8 @@
"@types/graceful-fs": "^4.1.3",
"@types/micromatch": "^4.0.7",
"@types/parse-json": "^4.0.0",
"esbuild": "^0.23.0",
"esbuild-register": "^3.4.0",
"semver": "^7.5.3",
"ts-node": "^10.5.0",
"typescript": "^5.0.4"
Expand Down
Loading
Loading