Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## main

### Features

- `[jest-config]` Supports Jest config file with `.mts` extension ([#15796](https://github.com/jestjs/jest/pull/15796))

## 30.0.5

### Features
Expand Down
120 changes: 120 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,120 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`on node <20.19.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 Node version <<REPLACED>> does not support loading .mts Jest config.
Please upgrade to ^20.19.0 || >=22.12.0"
`;

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>>
both with the native node TypeScript support and configured TypeScript loaders.
Errors were:
- SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]: Expected ';', got 'string literal (ll break this file yo, 'll break this file yo)'
- TSError: ⨯ Unable to compile TypeScript:
jest.config.mts(1,16): error TS2304: Cannot find name 'i'.
jest.config.mts(1,17): error TS1005: ';' expected.
jest.config.mts(1,39): error TS1002: Unterminated string literal."
`;

exports[`on node ^20.19.0 || >=22.12.0 <23.6.0 does not work with typed jest.config.ts 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
Current Node version <<REPLACED>> does not support loading typed .mts Jest config.
Please upgrade to ^23.6
Error: SyntaxError: Missing initializer in const declaration"
`;

exports[`on node ^20.19.0 || >=22.12.0 <23.6.0 work with untyped jest.config.mts 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node ^20.19.0 || >=22.12.0 <23.6.0 work with untyped 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 ^23.6 invalid JS in jest.config.mts (node with native TS support) 1`] = `
"Error: Jest: Failed to parse the TypeScript config file <<REPLACED>>
both with the native node TypeScript support and configured TypeScript loaders.
Errors were:
- SyntaxError [ERR_INVALID_TYPESCRIPT_SYNTAX]: x Expected ';', got 'string literal (ll break this file yo, 'll break this file yo)'
,----
1 | export default i'll break this file yo
: ^^^^^^^^^^^^^^^^^^^^^^
\`----
x Unterminated string constant
,----
1 | export default i'll break this file yo
: ^^^^^^^^^^^^^^^^^^^^^^
\`----

- TSError: ⨯ Unable to compile TypeScript:
jest.config.mts(1,16): error TS2304: Cannot find name 'i'.
jest.config.mts(1,17): error TS1005: ';' expected.
jest.config.mts(1,39): error TS1002: Unterminated string literal."
`;

exports[`on node ^23.6 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 ^23.6 traverses directory tree up until it finds jest.config 2`] = `
"PASS ../../../__tests__/a-giraffe.js
✓ giraffe
✓ abc"
`;

exports[`on node ^23.6 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 ^23.6 work with untyped jest.config.mts 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node ^23.6 work with untyped 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 ^23.6 works with tsconfig.json 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node ^23.6 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 ^23.6 works with typed jest.config.mts 1`] = `
"PASS __tests__/a-giraffe.js
✓ giraffe"
`;

exports[`on node ^23.6 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."
`;
248 changes: 248 additions & 0 deletions e2e/__tests__/jest.config.mts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/**
* 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));

onNodeVersions('<20.19.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',
});
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 Node version) (.+?) /m, '$1 <<REPLACED>> '),
).toMatchSnapshot();
expect(exitCode).toBe(1);
});
});

onNodeVersions('^20.19.0 || >=22.12.0 <23.6.0', () => {
test('work with untyped jest.config.mts', () => {
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 {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 Node 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('SyntaxError: Invalid or unexpected token');
expect(exitCode).toBe(1);
});
});

onNodeVersions('^23.6', () => {
test('work with untyped jest.config.mts', () => {
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 {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);
});
});
2 changes: 1 addition & 1 deletion packages/jest-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"micromatch": "^4.0.8",
"parse-json": "^5.2.0",
"pretty-format": "workspace:*",
"semver": "^7.7.2",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
Expand All @@ -66,7 +67,6 @@
"@types/parse-json": "^4.0.2",
"esbuild": "^0.25.5",
"esbuild-register": "^3.6.0",
"semver": "^7.7.2",
"ts-node": "^10.5.0",
"typescript": "^5.0.4"
},
Expand Down
Loading