Skip to content

Commit 1579380

Browse files
authored
fix(cfg): load jest.config.ts with TS loader specified in docblock pragma (#15839)
1 parent d7aacd9 commit 1579380

File tree

9 files changed

+82
-6
lines changed

9 files changed

+82
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
### Fixes
1313

1414
- `[babel-jest]` Export the `TransformerConfig` interface ([#15820](https://github.com/jestjs/jest/pull/15820))
15+
- `[jest-config]` Fix `jest.config.ts` with TS loader specified in docblock pragma ([#15839](https://github.com/jestjs/jest/pull/15839))
1516

1617
## 30.1.3
1718

e2e/__tests__/__snapshots__/jest.config.ts.test.ts.snap

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ jest.config.ts(1,17): error TS1005: ';' expected.
4545
jest.config.ts(1,39): error TS1002: Unterminated string literal."
4646
`;
4747
48+
exports[`on node ^23.6 load typed jest.config.ts with TS loader specified in docblock pragma 1`] = `
49+
"PASS __tests__/a-giraffe.js
50+
✓ giraffe"
51+
`;
52+
53+
exports[`on node ^23.6 load typed jest.config.ts with TS loader specified in docblock pragma 2`] = `
54+
"Test Suites: 1 passed, 1 total
55+
Tests: 1 passed, 1 total
56+
Snapshots: 0 total
57+
Time: <<REPLACED>>
58+
Ran all test suites."
59+
`;
60+
4861
exports[`traverses directory tree up until it finds jest.config 1`] = `
4962
" console.log
5063
<<REPLACED>>/jest-config-ts/some/nested/directory

e2e/__tests__/jest.config.ts.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,29 @@ onNodeVersions('^23.6', () => {
176176
).toMatchSnapshot();
177177
expect(exitCode).toBe(1);
178178
});
179+
180+
test('load typed jest.config.ts with TS loader specified in docblock pragma', () => {
181+
writeFiles(DIR, {
182+
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
183+
'foo.ts': 'export const a = () => {};',
184+
'jest.config.ts': `
185+
/** @jest-config-loader ts-node */
186+
import { a } from './foo'
187+
a();
188+
import type {Config} from 'jest';
189+
const config: Config = { testTimeout: 10000 };
190+
export default config;
191+
`,
192+
'package.json': '{}',
193+
});
194+
const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
195+
nodeOptions: '--no-warnings',
196+
});
197+
const {rest, summary} = extractSummary(stderr);
198+
expect(exitCode).toBe(0);
199+
expect(rest).toMatchSnapshot();
200+
expect(summary).toMatchSnapshot();
201+
});
179202
});
180203

181204
onNodeVersions('>=24', () => {

packages/jest-config/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"strip-json-comments": "^3.1.1"
6262
},
6363
"devDependencies": {
64+
"@jest/test-utils": "workspace:*",
6465
"@types/graceful-fs": "^4.1.9",
6566
"@types/micromatch": "^4.0.9",
6667
"@types/parse-json": "^4.0.2",

packages/jest-config/src/__tests__/readConfigFileAndSetRootDir.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import path from 'path';
99
import * as fs from 'graceful-fs';
1010
import {requireOrImportModule} from 'jest-util';
1111
import readConfigFileAndSetRootDir from '../readConfigFileAndSetRootDir';
12+
import {onNodeVersions} from '@jest/test-utils';
1213

1314
jest.mock('graceful-fs').mock('jest-util');
1415

@@ -167,3 +168,23 @@ describe('readConfigFileAndSetRootDir', () => {
167168
});
168169
});
169170
});
171+
172+
onNodeVersions('^24', () => {
173+
describe('TypeScript file', () => {
174+
test('reaches into 2nd loadout by TS loader if specified in docblock', async () => {
175+
jest
176+
.mocked(requireOrImportModule)
177+
.mockRejectedValueOnce(new Error('Module not found'));
178+
jest.mocked(fs.readFileSync).mockReturnValue(`
179+
/** @jest-config-loader tsx */
180+
export { testTimeout: 1_000 }
181+
`);
182+
const rootDir = path.resolve('some', 'path', 'to');
183+
await expect(
184+
readConfigFileAndSetRootDir(path.join(rootDir, 'jest.config.ts')),
185+
).rejects.toThrow(
186+
/Module not found\n.*'tsx' is not a valid TypeScript configuration loader./,
187+
);
188+
});
189+
});
190+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"extends": "../../../../tsconfig.test.json",
33
"include": ["./**/*"],
4-
"references": [{"path": "../../"}]
4+
"references": [{"path": "../../"}, {"path": "../../../test-utils"}]
55
}

packages/jest-config/src/readConfigFileAndSetRootDir.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as fs from 'graceful-fs';
1111
import parseJson from 'parse-json';
1212
import stripJsonComments from 'strip-json-comments';
1313
import type {Config} from '@jest/types';
14-
import {extract, parse} from 'jest-docblock';
14+
import {type Pragmas, extract, parse} from 'jest-docblock';
1515
import {interopRequireDefault, requireOrImportModule} from 'jest-util';
1616
import {
1717
JEST_CONFIG_EXT_CTS,
@@ -47,10 +47,15 @@ export default async function readConfigFileAndSetRootDir(
4747
configObject = await requireOrImportModule<any>(configPath);
4848
} catch (requireOrImportModuleError) {
4949
if (!(requireOrImportModuleError instanceof SyntaxError)) {
50-
throw requireOrImportModuleError;
50+
if (!hasTsLoaderExplicitlyConfigured(configPath)) {
51+
throw requireOrImportModuleError;
52+
}
5153
}
5254
try {
53-
// Likely ESM in a file interpreted as CJS, which means it needs to be
55+
// There are various reasons of failed loadout of Jest config in Typescript:
56+
// 1. User has specified a TypeScript loader in the docblock and
57+
// desire non-native compilation (https://github.com/jestjs/jest/issues/15837)
58+
// 2. Likely ESM in a file interpreted as CJS, which means it needs to be
5459
// compiled. We ignore the error and try to load it with a loader.
5560
configObject = await loadTSConfigFile(configPath);
5661
} catch (loadTSConfigFileError) {
@@ -120,11 +125,22 @@ export default async function readConfigFileAndSetRootDir(
120125
// Load the TypeScript configuration
121126
let extraTSLoaderOptions: Record<string, unknown>;
122127

128+
const hasTsLoaderExplicitlyConfigured = (configPath: string): boolean => {
129+
const docblockPragmas = loadDocblockPragmasInConfig(configPath);
130+
const tsLoader = docblockPragmas['jest-config-loader'];
131+
return !Array.isArray(tsLoader) && (tsLoader ?? '').trim() !== '';
132+
};
133+
134+
const loadDocblockPragmasInConfig = (configPath: string): Pragmas => {
135+
const docblockPragmas = parse(extract(fs.readFileSync(configPath, 'utf8')));
136+
return docblockPragmas;
137+
};
138+
123139
const loadTSConfigFile = async (
124140
configPath: string,
125141
): Promise<Config.InitialOptions> => {
126142
// Get registered TypeScript compiler instance
127-
const docblockPragmas = parse(extract(fs.readFileSync(configPath, 'utf8')));
143+
const docblockPragmas = loadDocblockPragmasInConfig(configPath);
128144
const tsLoader = docblockPragmas['jest-config-loader'] || 'ts-node';
129145
const docblockTSLoaderOptions = docblockPragmas['jest-config-loader-options'];
130146

packages/jest-docblock/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {EOL} from 'os';
99
import detectNewline from 'detect-newline';
1010

11-
type Pragmas = Record<string, string | Array<string>>;
11+
export type Pragmas = Record<string, string | Array<string>>;
1212

1313
const commentEndRe = /\*\/$/;
1414
const commentStartRe = /^\/\*\*?/;

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13588,6 +13588,7 @@ __metadata:
1358813588
"@jest/get-type": "workspace:*"
1358913589
"@jest/pattern": "workspace:*"
1359013590
"@jest/test-sequencer": "workspace:*"
13591+
"@jest/test-utils": "workspace:*"
1359113592
"@jest/types": "workspace:*"
1359213593
"@types/graceful-fs": "npm:^4.1.9"
1359313594
"@types/micromatch": "npm:^4.0.9"

0 commit comments

Comments
 (0)