Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ecbdcf9
feat(jest-config): supports Jest config file with .mts extension
hainenber Aug 24, 2025
73135e8
feat(jest-config): supports untyped`.mts` config file
hainenber Aug 26, 2025
4637296
chore: add CHANGELOG entry
hainenber Aug 26, 2025
e789de5
Update readConfigFileAndSetRootDir.ts
cpojer Aug 27, 2025
dcad33b
feat(cfg): enhance error message to inform user of default type strip…
hainenber Sep 27, 2025
7e8a3f5
Merge branch 'main' into feat/support-jest-config-with-mts-extension
hainenber Sep 27, 2025
18e8712
Merge branch 'main' into feat/support-jest-config-with-mts-extension
hainenber Sep 28, 2025
0b24295
chore: address failed tests with updated snapshot + expand case for j…
hainenber Oct 11, 2025
469862b
Merge branch 'main' into feat/support-jest-config-with-mts-extension
hainenber Oct 11, 2025
f13b60b
chore: address failed test with updated snapshot and refactors
hainenber Oct 11, 2025
7517cfb
chore: fix test content for typed jest.config.mts
hainenber Oct 11, 2025
0da1b27
chore: update snapshot for failed test
hainenber Oct 11, 2025
ae42957
feat(jest-config): revamp config loadout for .mts config to be JS run…
hainenber Oct 15, 2025
61f8696
chore: reposition CHANGELOG entry
hainenber Oct 15, 2025
8004352
chore: fix lint issues
hainenber Oct 15, 2025
2183aa3
chore: refresh yarn cache
hainenber Oct 15, 2025
e81a7b5
feat: guide users to use native runtime to load .mts Jest config
hainenber Oct 16, 2025
8d4363d
chore: update snapshot
hainenber Oct 16, 2025
15c06e3
Merge branch 'main' into feat/support-jest-config-with-mts-extension
hainenber Oct 26, 2025
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 @@ -3,6 +3,7 @@
### Features

- `[jest-config]` Add `defineConfig` and `mergeConfig` helpers for type-safe Jest config ([#15844](https://github.com/jestjs/jest/pull/15844))
- `[jest-config]` Supports Jest config file with `.mts` extension ([#15796](https://github.com/jestjs/jest/pull/15796))

### Fixes

Expand Down
104 changes: 104 additions & 0 deletions e2e/__tests__/__snapshots__/jest.config.mts.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`do not work with jest.config.mts when TS loader is used 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
Loading .mts Jest config with external loaders is discouraged.
Please use a JS runtime that supports process.features.require_module and process.features.typescript"
`;

exports[`on node >=22.18.0 invalid JS in jest.config.mts (node with native TS support) 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]: Expected ';', got 'string literal (ll break this file yo, 'll break this file yo)'"
`;

exports[`on node >=22.18.0 traverses directory tree up until it finds jest.config 1`] = `
" console.log
<<REPLACED>>/jest-config-ts/some/nested/directory

at Object.<anonymous> (__tests__/a-giraffe.js:3:27)
"
`;

exports[`on node >=22.18.0 traverses directory tree up until it finds jest.config 2`] = `
"PASS ../../../__tests__/a-giraffe.js
✓ giraffe
✓ abc"
`;

exports[`on node >=22.18.0 traverses directory tree up until it finds jest.config 3`] = `
"Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;

exports[`on node >=22.18.0 work with untyped jest.config.mts for Node versions with default type stripping 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node >=22.18.0 work with untyped jest.config.mts for Node versions with default type stripping 2`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;

exports[`on node >=22.18.0 works with tsconfig.json 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node >=22.18.0 works with tsconfig.json 2`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;

exports[`on node >=22.18.0 works with typed jest.config.mts 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node >=22.18.0 works with typed jest.config.mts 2`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;

exports[`on node >=24 invalid JS in jest.config.mts (node with native TS support) 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]: Expected ';', got 'string literal (ll break this file yo, 'll break this file yo)'"
`;

exports[`on node ^20.19.0 || >=22.12.0 <22.18.0 does not work with jest.config.mts when require(esm) is not supported 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
Current JS runtime version <<REPLACED>> does not support loading .mts Jest config.
Please upgrade your JS runtime to support process.features.require_module"
`;

exports[`on node ^20.19.0 || >=22.12.0 <22.18.0 does not work with typed jest.config.ts 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
Current JS runtime version <<REPLACED>> does not support loading typed .mts Jest config.
Please upgrade your JS runtime to support process.features.typescript
Error: SyntaxError: Unexpected token '{'"
`;

exports[`on node ^20.19.0 || >=22.12.0 <22.18.0 work with untyped jest.config.mts for Node versions without default type stripping 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node ^20.19.0 || >=22.12.0 <22.18.0 work with untyped jest.config.mts for Node versions without default type stripping 2`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;
278 changes: 278 additions & 0 deletions e2e/__tests__/jest.config.mts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/**
* 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.
*/

import * as path from 'path';
import {onNodeVersions} from '@jest/test-utils';
import {cleanup, extractSummary, writeFiles} from '../Utils';
import runJest from '../runJest';

const DIR = path.resolve(__dirname, '../jest-config-ts');

beforeEach(() => cleanup(DIR));
afterAll(() => cleanup(DIR));

test('do not work with jest.config.mts when TS loader is used', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': `
/** @jest-config-loader esbuild-register */
import type {Config} from 'jest';
const config: Config = {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js' };
export default config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
expect(
stderr
// Remove the stack trace from the error message
.slice(0, Math.max(0, stderr.indexOf('at readConfigFileAndSetRootDir')))
.trim()
// Replace the path to the config file with a placeholder
.replace(
/(Error: Jest: Failed to parse the TypeScript config file).*$/m,
'$1 <<REPLACED>>',
),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});

onNodeVersions('^20.19.0 || >=22.12.0 <22.18.0', () => {
test('does not work with jest.config.mts when require(esm) is not supported', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts':
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings --no-experimental-require-module',
});
expect(
stderr
// Remove the stack trace from the error message
.slice(0, Math.max(0, stderr.indexOf('at readConfigFileAndSetRootDir')))
.trim()
// Replace the path to the config file with a placeholder
.replace(
/(Error: Jest: Failed to parse the TypeScript config file).*$/m,
'$1 <<REPLACED>>',
)
// Replace Node version with placeholder
.replace(/(Current JS runtime version) (.+?) /m, '$1 <<REPLACED>> '),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});

test('work with untyped jest.config.mts for Node versions without default type stripping', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts':
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
});

test('does not work with typed jest.config.ts', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': `
import type {Config} from 'jest';
const config: Config = {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js' };
export default config;
`,
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
expect(
stderr
// Remove the stack trace from the error message
.slice(0, Math.max(0, stderr.indexOf('at readConfigFileAndSetRootDir')))
.trim()
// Replace the path to the config file with a placeholder
.replace(
/(Error: Jest: Failed to parse the TypeScript config file).*$/m,
'$1 <<REPLACED>>',
)
// Replace Node version with
.replace(/(Current JS runtime version) (.+?) /m, '$1 <<REPLACED>> '),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});

test('invalid JS in jest.config.mts', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': "export default i'll break this file yo",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false']);
expect(stderr).toMatch('does not support loading typed .mts Jest config');
expect(stderr).toMatch(
'Please upgrade your JS runtime to support process.features.typescript',
);
expect(exitCode).toBe(1);
});
});

onNodeVersions('>=22.18.0', () => {
test('work with untyped jest.config.mts for Node versions with default type stripping', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts':
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
});

test('works with typed jest.config.mts', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': `
import type {Config} from 'jest';
const config: Config = {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js' };
export default config;
`,
'package.json': '{"type": "commonjs"}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
});

test('works with tsconfig.json', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts':
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
'package.json': '{}',
'tsconfig.json': '{ "compilerOptions": { "module": "esnext" } }',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
});

test('traverses directory tree up until it finds jest.config', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': `
const slash = require('slash');
test('giraffe', () => expect(1).toBe(1));
test('abc', () => console.log(slash(process.cwd())));
`,
'jest.config.mts':
"export default {testEnvironment: 'jest-environment-node', testRegex: '.*-giraffe.js'};",
'package.json': '{}',
'some/nested/directory/file.js': '// nothing special',
});

const {stderr, exitCode, stdout} = runJest(
path.join(DIR, 'some', 'nested', 'directory'),
['-w=1', '--ci=false'],
{nodeOptions: '--no-warnings', skipPkgJsonCheck: true},
);

// Snapshot the console.logged `process.cwd()` and make sure it stays the same
expect(
stdout
.replaceAll(/^\W+(.*)e2e/gm, '<<REPLACED>>')
// slightly different log in node versions >= 23
.replace('at Object.log', 'at Object.<anonymous>'),
).toMatchSnapshot();

const {rest, summary} = extractSummary(stderr);
expect(exitCode).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
});

test('invalid JS in jest.config.mts (node with native TS support)', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': "export default i'll break this file yo",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
expect(
stderr
// Remove the stack trace from the error message
.slice(0, Math.max(0, stderr.indexOf('at readConfigFileAndSetRootDir')))
.trim()
// Replace the path to the config file with a placeholder
.replace(
/(Error: Jest: Failed to parse the TypeScript config file).*$/m,
'$1 <<REPLACED>>',
),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});
});

onNodeVersions('>=24', () => {
// todo fixme
// eslint-disable-next-line jest/no-identical-title
test('invalid JS in jest.config.mts (node with native TS support)', () => {
writeFiles(DIR, {
'__tests__/a-giraffe.js': "test('giraffe', () => expect(1).toBe(1));",
'jest.config.mts': "export default i'll break this file yo",
'package.json': '{}',
});

const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false'], {
nodeOptions: '--no-warnings',
});
expect(
stderr
// Remove the stack trace from the error message
.slice(0, Math.max(0, stderr.indexOf('at readConfigFileAndSetRootDir')))
.trim()
// Replace the path to the config file with a placeholder
.replace(
/(Error: Jest: Failed to parse the TypeScript config file).*$/m,
'$1 <<REPLACED>>',
),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});
});
Loading
Loading