From f9cc3c01edb37bfca15e0df0e1e9f1c05ddb82da Mon Sep 17 00:00:00 2001 From: ahnpnl Date: Sun, 23 Jun 2024 12:48:32 +0200 Subject: [PATCH] feat(presets): introduce util functions to create presets This change will allow users to easily extend existing presets to override certain options --- src/cli/cli.spec.ts | 191 +++++++++--------- src/cli/config/init.ts | 70 ++++--- src/cli/index.ts | 10 +- src/constants.ts | 16 ++ .../create-jest-preset.spec.ts.snap | 131 +++++++++++- src/presets/create-jest-preset.spec.ts | 176 +++++++++++----- src/presets/create-jest-preset.ts | 138 ++++++++++++- src/types.ts | 21 ++ src/utils/messages.ts | 3 +- 9 files changed, 568 insertions(+), 188 deletions(-) diff --git a/src/cli/cli.spec.ts b/src/cli/cli.spec.ts index 0ed1abae39..a8b29a2d5c 100644 --- a/src/cli/cli.spec.ts +++ b/src/cli/cli.spec.ts @@ -117,20 +117,21 @@ describe('config', () => { '--tsconfig', 'tsconfig.test.json', '--jsdom', - '--no-jest-preset', + '--jest-preset', '--js', 'ts', '--babel', ] - it('should create a jest.config.json (without options)', async () => { + it('should create a jest.config.js (without options)', async () => { fs.existsSync.mockImplementation((f) => f === FAKE_PKG) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fs.readFileSync.mockImplementation((f): any => { - if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) - throw new Error('ENOENT') - }) - expect.assertions(2) + fs.readFileSync + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementationOnce((f): any => { + if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) + throw new Error('ENOENT') + }) + expect.assertions(3) const res = await runCli(...noOption) expect(res).toEqual({ @@ -141,26 +142,26 @@ Jest configuration written to "${normalize('/foo/bar/jest.config.js')}". `, stdout: '', }) - expect(fs.writeFileSync.mock.calls).toEqual([ - [ - normalize('/foo/bar/jest.config.js'), - `/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -};`, - ], - ]) + expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.js')) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(` + "/** @type {import('ts-jest').JestConfigWithTsJest} **/ + module.exports = { + testEnvironment: 'node', + transform: { + '^.+.tsx?$': 'ts-jest', + }, + };" + `) }) - it('should create a jest.config.foo.json (with all options set)', async () => { + it('should create a jest.config.foo.js (with all options set)', async () => { fs.existsSync.mockImplementation((f) => f === FAKE_PKG) // eslint-disable-next-line @typescript-eslint/no-explicit-any - fs.readFileSync.mockImplementation((f): any => { + fs.readFileSync.mockImplementationOnce((f): any => { if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) throw new Error('ENOENT') }) - expect.assertions(2) + expect.assertions(3) const res = await runCli(...fullOptions, 'jest.config.foo.js') expect(res).toEqual({ @@ -171,29 +172,60 @@ Jest configuration written to "${normalize('/foo/bar/jest.config.foo.js')}". `, stdout: '', }) - expect(fs.writeFileSync.mock.calls).toEqual([ - [ - normalize('/foo/bar/jest.config.foo.js'), - `const { jsWithTs: tsjPreset } = require('ts-jest/presets'); - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - ...tsjPreset, - transform: { - '^.+\\\\.[tj]sx?$': ['ts-jest', { - tsconfig: 'tsconfig.test.json', - babelConfig: true, - }], - }, -};`, - ], - ]) + expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.foo.js')) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(` + "/** @type {import('ts-jest').JestConfigWithTsJest} **/ + module.exports = { + testEnvironment: 'jsdom', + transform: { + '^.+.[tj]sx?$': + [ + 'ts-jest', + { + tsconfig: 'tsconfig.test.json' + } + ] + , + }, + };" + `) + }) + + it('should create jest config with type "module" package.json', async () => { + fs.existsSync.mockImplementation((f) => f === FAKE_PKG) + fs.readFileSync + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementationOnce((f): any => { + if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0', type: 'module' }) + throw new Error('ENOENT') + }) + expect.assertions(3) + const res = await runCli(...noOption) + + expect(res).toEqual({ + exitCode: 0, + log: '', + stderr: ` +Jest configuration written to "${normalize('/foo/bar/jest.config.js')}". +`, + stdout: '', + }) + expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.js')) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(` + "/** @type {import('ts-jest').JestConfigWithTsJest} **/ + export default { + testEnvironment: 'node', + transform: { + '^.+.tsx?$': 'ts-jest', + }, + };" + `) }) it('should update package.json (without options)', async () => { fs.existsSync.mockImplementation((f) => f === FAKE_PKG) // eslint-disable-next-line @typescript-eslint/no-explicit-any - fs.readFileSync.mockImplementation((f): any => { + fs.readFileSync.mockImplementationOnce((f): any => { if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) throw new Error('ENOENT') }) @@ -225,12 +257,13 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}". it('should update package.json (with all options set)', async () => { fs.existsSync.mockImplementation((f) => f === FAKE_PKG) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fs.readFileSync.mockImplementation((f): any => { - if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) - throw new Error('ENOENT') - }) - expect.assertions(2) + fs.readFileSync + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementationOnce((f): any => { + if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' }) + throw new Error('ENOENT') + }) + expect.assertions(3) const res = await runCli(...fullOptions, 'package.json') expect(res).toEqual({ @@ -241,26 +274,16 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}". `, stdout: '', }) - expect(fs.writeFileSync.mock.calls).toEqual([ - [ - normalize('/foo/bar/package.json'), - `{ - "name": "mock", - "version": "0.0.0-mock.0", - "jest": { - "transform": { - "^.+\\\\.[tj]sx?$": [ - "ts-jest", - { - "tsconfig": "tsconfig.test.json", - "babelConfig": true - } - ] - } - } -}`, - ], - ]) + expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/package.json')) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(` + "{ + "name": "mock", + "version": "0.0.0-mock.0", + "jest": { + "preset": "ts-jest/presets/js-with-ts" + } + }" + `) }) it('should output help', async () => { @@ -289,46 +312,16 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}". Options: --force Discard any existing Jest config - --js ts|babel Process .js files with ts-jest if 'ts' or with + --js ts|babel Process '.js' files with ts-jest if 'ts' or with babel-jest if 'babel' - --no-jest-preset Disable the use of Jest presets + --jest-preset Toggle using preset --tsconfig Path to the tsconfig.json file - --babel Pipe babel-jest after ts-jest - --jsdom Use jsdom as test environment instead of node + --babel Enable using Babel to process 'js' resulted content from 'ts-jest' processing + --jsdom Use 'jsdom' as test environment instead of 'node' ", } `) }) - - it('should create jest config with type "module" package.json', async () => { - fs.existsSync.mockImplementation((f) => f === FAKE_PKG) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fs.readFileSync.mockImplementation((f): any => { - if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0', type: 'module' }) - throw new Error('ENOENT') - }) - expect.assertions(2) - const res = await runCli(...noOption) - - expect(res).toEqual({ - exitCode: 0, - log: '', - stderr: ` -Jest configuration written to "${normalize('/foo/bar/jest.config.js')}". -`, - stdout: '', - }) - expect(fs.writeFileSync.mock.calls).toEqual([ - [ - normalize('/foo/bar/jest.config.js'), - `/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { - preset: 'ts-jest', - testEnvironment: 'node', -};`, - ], - ]) - }) }) describe('migrate', () => { diff --git a/src/cli/config/init.ts b/src/cli/config/init.ts index 182650a693..29ab84837b 100644 --- a/src/cli/config/init.ts +++ b/src/cli/config/init.ts @@ -7,11 +7,13 @@ import { existsSync, readFileSync, writeFileSync } from 'fs' import { basename, join } from 'path' +import ejs from 'ejs' import { stringify as stringifyJson5 } from 'json5' import type { CliCommand, CliCommandArgs } from '..' +import { JEST_CONFIG_EJS_TEMPLATE, TS_JS_TRANSFORM_PATTERN, TS_TRANSFORM_PATTERN } from '../../constants' import type { JestConfigWithTsJest, TsJestTransformerOptions } from '../../types' -import { type TsJestPresetDescriptor, defaults, jsWIthBabel, jsWithTs } from '../helpers/presets' +import { type TsJestPresetDescriptor, defaults, jsWIthBabel, jsWithTs, JestPresetNames } from '../helpers/presets' /** * @internal @@ -107,34 +109,38 @@ export const run: CliCommand = async (args: CliCommandArgs /* , logger: Logger * } body = JSON.stringify({ ...pkgJson, jest: jestConfig }, undefined, ' ') } else { - // js config - const content: string[] = [] - if (!jestPreset) { - content.push(`${preset.jsImport('tsjPreset')};`, '') - } - content.push(`/** @type {import('ts-jest').JestConfigWithTsJest} */`) - const usesModules = pkgJson.type === 'module' - content.push(usesModules ? 'export default {' : 'module.exports = {') - - if (jestPreset) { - content.push(` preset: '${preset.name}',`) - } else { - content.push(' ...tsjPreset,') - } - if (!jsdom) content.push(" testEnvironment: 'node',") - - if (tsconfig || shouldPostProcessWithBabel) { - content.push(' transform: {') - content.push(" '^.+\\\\.[tj]sx?$': ['ts-jest', {") - if (tsconfig) content.push(` tsconfig: ${stringifyJson5(tsconfig)},`) - if (shouldPostProcessWithBabel) content.push(' babelConfig: true,') - content.push(' }],') - content.push(' },') + let transformPattern = TS_TRANSFORM_PATTERN + let transformValue = !tsconfig + ? `'ts-jest'` + : ` + [ + 'ts-jest', + { + tsconfig: ${stringifyJson5(tsconfig)} + } + ] + ` + if (preset.name === JestPresetNames.jsWithTs) { + transformPattern = TS_JS_TRANSFORM_PATTERN + } else if (preset.name === JestPresetNames.jsWIthBabel) { + transformValue = !tsconfig + ? `'ts-jest'` + : ` + [ + 'ts-jest', + { + babelConfig: true, + tsconfig: ${stringifyJson5(tsconfig)} + } + ] + ` } - content.push('};') - - // join all together - body = content.join('\n') + body = ejs.render(JEST_CONFIG_EJS_TEMPLATE, { + exportKind: pkgJson.type === 'module' ? 'export default' : 'module.exports =', + testEnvironment: jsdom ? 'jsdom' : 'node', + transformPattern, + transformValue, + }) } writeFileSync(filePath, body) @@ -160,11 +166,11 @@ Arguments: Options: --force Discard any existing Jest config - --js ts|babel Process .js files with ts-jest if 'ts' or with + --js ts|babel Process '.js' files with ts-jest if 'ts' or with babel-jest if 'babel' - --no-jest-preset Disable the use of Jest presets + --jest-preset Toggle using preset --tsconfig Path to the tsconfig.json file - --babel Pipe babel-jest after ts-jest - --jsdom Use jsdom as test environment instead of node + --babel Enable using Babel to process 'js' resulted content from 'ts-jest' processing + --jsdom Use 'jsdom' as test environment instead of 'node' `) } diff --git a/src/cli/index.ts b/src/cli/index.ts index 3f9a9f421f..0969e38fa6 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -2,6 +2,7 @@ import { LogContexts, type Logger } from 'bs-logger' import type { Arguments } from 'yargs' import yargsParser from 'yargs-parser' +import type { TsJestTransformerOptions } from '../types' import { rootLogger } from '../utils' const VALID_COMMANDS = ['help', 'config:migrate', 'config:init'] @@ -11,7 +12,14 @@ const logger = rootLogger.child({ [LogContexts.namespace]: 'cli', [LogContexts.a /** * @internal */ -export type CliCommandArgs = Omit & { _: Array } +export type CliCommandArgs = Omit & { _: Array } & { + jestPreset?: boolean + force?: boolean + tsconfig?: TsJestTransformerOptions['tsconfig'] + babel?: boolean + jsdom?: boolean + js?: 'ts' | 'babel' +} /** * @internal */ diff --git a/src/constants.ts b/src/constants.ts index d7330d1187..18c85f92c2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,12 @@ export const DECLARATION_TYPE_EXT = '.d.ts' export const JS_JSX_EXTENSIONS = ['.js', '.jsx'] export const TS_TSX_REGEX = /\.[cm]?tsx?$/ export const JS_JSX_REGEX = /\.[cm]?jsx?$/ +export const TS_TRANSFORM_PATTERN = '^.+.tsx?$' +export const ESM_TS_TRANSFORM_PATTERN = '^.+\\.m?tsx?$' +export const TS_JS_TRANSFORM_PATTERN = '^.+.[tj]sx?$' +export const ESM_TS_JS_TRANSFORM_PATTERN = '^.+\\.m?[tj]sx?$' +export const JS_TRANSFORM_PATTERN = '^.+.jsx?$' +export const ESM_JS_TRANSFORM_PATTERN = '^.+\\.m?jsx?$' // `extensionsToTreatAsEsm` will throw error with `.mjs` export const TS_EXT_TO_TREAT_AS_ESM = ['.ts', '.tsx', '.mts'] export const JS_EXT_TO_TREAT_AS_ESM = ['.jsx'] @@ -11,3 +17,13 @@ export const JS_EXT_TO_TREAT_AS_ESM = ['.jsx'] * See https://jestjs.io/docs/en/configuration#testmatch-arraystring */ export const DEFAULT_JEST_TEST_MATCH = ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'] +/** + * @internal + */ +export const JEST_CONFIG_EJS_TEMPLATE = `/** @type {import('ts-jest').JestConfigWithTsJest} **/ +<%= exportKind %> { + testEnvironment: '<%= testEnvironment %>', + transform: { + '<%= transformPattern %>': <%- transformValue %>, + }, +};` diff --git a/src/presets/__snapshots__/create-jest-preset.spec.ts.snap b/src/presets/__snapshots__/create-jest-preset.spec.ts.snap index 32df303d8c..51c3ae012d 100644 --- a/src/presets/__snapshots__/create-jest-preset.spec.ts.snap +++ b/src/presets/__snapshots__/create-jest-preset.spec.ts.snap @@ -1,6 +1,123 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`create-jest-preset should return correct preset 1`] = ` +exports[`create-jest-preset CJS presets createLegacyDefaultPreset should return preset config 1`] = ` +{ + "transform": { + "^.+.tsx?$": [ + "ts-jest", + { + "tsconfig": "tsconfig.spec.json", + }, + ], + }, +} +`; + +exports[`create-jest-preset CJS presets createLegacyJsWithTsPreset should return preset config 1`] = ` +{ + "transform": { + "^.+.[tj]sx?$": [ + "ts-jest", + { + "isolatedModules": true, + "tsconfig": "tsconfig.spec.json", + }, + ], + }, +} +`; + +exports[`create-jest-preset CJS presets createLegacyWithBabelPreset should return preset config 1`] = ` +{ + "transform": { + "^.+.jsx?$": [ + "babel-jest", + { + "babelrc": true, + }, + ], + "^.+.tsx?$": [ + "ts-jest", + { + "babelConfig": true, + "tsconfig": "tsconfig.spec.json", + }, + ], + }, +} +`; + +exports[`create-jest-preset ESM presets createLegacyDefaultEsmPreset should return preset config 1`] = ` +{ + "extensionsToTreatAsEsm": [ + ".jsx", + ".ts", + ".tsx", + ".mts", + ], + "transform": { + "^.+\\.m?tsx?$": [ + "ts-jest", + { + "tsconfig": "tsconfig.spec.json", + "useESM": true, + }, + ], + }, +} +`; + +exports[`create-jest-preset ESM presets createLegacyJsWithBabelEsmPreset should return preset config 1`] = ` +{ + "extensionsToTreatAsEsm": [ + ".jsx", + ".ts", + ".tsx", + ".mts", + ], + "transform": { + "^.+\\.m?jsx?$": [ + "babel-jest", + { + "babelrc": true, + }, + ], + "^.+\\.m?tsx?$": [ + "ts-jest", + { + "babelConfig": { + "babelrc": true, + }, + "tsconfig": "tsconfig.spec.json", + "useESM": true, + }, + ], + }, +} +`; + +exports[`create-jest-preset ESM presets createLegacyJsWithTsEsmPreset should return preset config 1`] = ` +{ + "extensionsToTreatAsEsm": [ + ".jsx", + ".ts", + ".tsx", + ".mts", + ], + "transform": { + "^.+\\.m?[tj]sx?$": [ + "ts-jest", + { + "isolatedModules": true, + "tsconfig": "tsconfig.spec.json", + "useESM": true, + }, + ], + }, +} +`; + +exports[`create-jest-preset createJestPreset should return correct preset 1`] = ` { "transform": { "^.+\\.tsx?$": [ @@ -11,7 +128,7 @@ exports[`create-jest-preset should return correct preset 1`] = ` } `; -exports[`create-jest-preset should return correct preset 2`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 2`] = ` { "transform": { "^.+\\.tsx?$": [ @@ -22,7 +139,7 @@ exports[`create-jest-preset should return correct preset 2`] = ` } `; -exports[`create-jest-preset should return correct preset 3`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 3`] = ` { "transform": { "^.+\\.[tj]sx?$": [ @@ -33,7 +150,7 @@ exports[`create-jest-preset should return correct preset 3`] = ` } `; -exports[`create-jest-preset should return correct preset 4`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 4`] = ` { "transform": { "^.+\\.[tj]sx?$": [ @@ -44,7 +161,7 @@ exports[`create-jest-preset should return correct preset 4`] = ` } `; -exports[`create-jest-preset should return correct preset 5`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 5`] = ` { "transform": { "^.+\\.tsx?$": [ @@ -55,7 +172,7 @@ exports[`create-jest-preset should return correct preset 5`] = ` } `; -exports[`create-jest-preset should return correct preset 6`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 6`] = ` { "moduleFileExtensions": [ "bar", @@ -73,7 +190,7 @@ exports[`create-jest-preset should return correct preset 6`] = ` } `; -exports[`create-jest-preset should return correct preset 7`] = ` +exports[`create-jest-preset createJestPreset should return correct preset 7`] = ` { "extensionsToTreatAsEsm": [ ".jsx", diff --git a/src/presets/create-jest-preset.spec.ts b/src/presets/create-jest-preset.spec.ts index a4b7d424da..630fe38794 100644 --- a/src/presets/create-jest-preset.spec.ts +++ b/src/presets/create-jest-preset.spec.ts @@ -1,54 +1,136 @@ import { JS_EXT_TO_TREAT_AS_ESM, TS_EXT_TO_TREAT_AS_ESM } from '../constants' -import { createJestPreset } from './create-jest-preset' +import { + createJestPreset, + createLegacyDefaultPreset, + createLegacyWithBabelPreset, + createLegacyJsWithTsPreset, + createLegacyDefaultEsmPreset, + createLegacyJsWithTsEsmPreset, + createLegacyWithBabelEsmPreset, +} from './create-jest-preset' describe('create-jest-preset', () => { - const baseExtraOptions = { - testMatch: ['foo'], - moduleFileExtensions: ['bar'], - transform: { foo: 'bar' }, - } - - test.each([ - { - legacy: true, - allowJs: undefined, - extraOptions: undefined, - }, - { - legacy: false, - allowJs: false, - extraOptions: undefined, - }, - { - legacy: true, - allowJs: true, - extraOptions: undefined, - }, - { - legacy: false, - allowJs: true, - extraOptions: {}, - }, - { - legacy: true, - allowJs: false, - extraOptions: {}, - }, - { - legacy: false, - allowJs: false, - extraOptions: baseExtraOptions, - }, - { - legacy: true, - allowJs: true, - extraOptions: { - ...baseExtraOptions, - extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + describe('createJestPreset', () => { + const baseExtraOptions = { + testMatch: ['foo'], + moduleFileExtensions: ['bar'], + transform: { foo: 'bar' }, + } + + test.each([ + { + legacy: true, + allowJs: undefined, + extraOptions: undefined, + }, + { + legacy: false, + allowJs: false, + extraOptions: undefined, + }, + { + legacy: true, + allowJs: true, + extraOptions: undefined, + }, + { + legacy: false, + allowJs: true, + extraOptions: {}, }, - }, - ])('should return correct preset', (data) => { - expect(createJestPreset(data.legacy, data.allowJs, data.extraOptions)).toMatchSnapshot() + { + legacy: true, + allowJs: false, + extraOptions: {}, + }, + { + legacy: false, + allowJs: false, + extraOptions: baseExtraOptions, + }, + { + legacy: true, + allowJs: true, + extraOptions: { + ...baseExtraOptions, + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + }, + }, + ])('should return correct preset', (data) => { + expect(createJestPreset(data.legacy, data.allowJs, data.extraOptions)).toMatchSnapshot() + }) + }) + + describe('CJS presets', () => { + describe('createLegacyDefaultPreset', () => { + it('should return preset config', () => { + expect( + createLegacyDefaultPreset({ + tsconfig: 'tsconfig.spec.json', + }), + ).toMatchSnapshot() + }) + }) + + describe('createLegacyJsWithTsPreset', () => { + it('should return preset config', () => { + expect( + createLegacyJsWithTsPreset({ + tsconfig: 'tsconfig.spec.json', + isolatedModules: true, + }), + ).toMatchSnapshot() + }) + }) + + describe('createLegacyWithBabelPreset', () => { + it('should return preset config', () => { + expect( + createLegacyWithBabelPreset({ + tsconfig: 'tsconfig.spec.json', + babelConfig: { + babelrc: true, + }, + }), + ).toMatchSnapshot() + }) + }) + }) + + describe('ESM presets', () => { + describe('createLegacyDefaultEsmPreset', () => { + it('should return preset config', () => { + expect( + createLegacyDefaultEsmPreset({ + tsconfig: 'tsconfig.spec.json', + }), + ).toMatchSnapshot() + }) + }) + + describe('createLegacyJsWithTsEsmPreset', () => { + it('should return preset config', () => { + expect( + createLegacyJsWithTsEsmPreset({ + tsconfig: 'tsconfig.spec.json', + isolatedModules: true, + }), + ).toMatchSnapshot() + }) + }) + + describe('createLegacyJsWithBabelEsmPreset', () => { + it('should return preset config', () => { + expect( + createLegacyWithBabelEsmPreset({ + tsconfig: 'tsconfig.spec.json', + babelConfig: { + babelrc: true, + }, + }), + ).toMatchSnapshot() + }) + }) }) }) diff --git a/src/presets/create-jest-preset.ts b/src/presets/create-jest-preset.ts index 4901a65579..4af0d9e561 100644 --- a/src/presets/create-jest-preset.ts +++ b/src/presets/create-jest-preset.ts @@ -1,10 +1,30 @@ import type { Config } from '@jest/types' -import type { TsJestPresets, TsJestTransformerOptions } from '../types' +import { + TS_EXT_TO_TREAT_AS_ESM, + JS_EXT_TO_TREAT_AS_ESM, + TS_TRANSFORM_PATTERN, + TS_JS_TRANSFORM_PATTERN, + JS_TRANSFORM_PATTERN, + ESM_TS_TRANSFORM_PATTERN, + ESM_TS_JS_TRANSFORM_PATTERN, + ESM_JS_TRANSFORM_PATTERN, +} from '../constants' +import type { + BabelConfig, + DefaultPreset, + JsWithBabelPreset, + JsWithTsPreset, + TsJestPresets, + TsJestTransformerOptions, +} from '../types' import { rootLogger } from '../utils' const logger = rootLogger.child({ namespace: 'jest-preset' }) +/** + * @deprecated use other functions below instead + */ export function createJestPreset( legacy = false, allowJs = false, @@ -28,3 +48,119 @@ export function createJestPreset( }, } } + +export function createLegacyDefaultPreset( + tsJestTransformOptions: Omit = {}, +): DefaultPreset { + logger.debug('creating default legacy CJS Jest preset') + + return { + transform: { + [TS_TRANSFORM_PATTERN]: ['ts-jest', tsJestTransformOptions], + }, + } +} + +export function createLegacyJsWithTsPreset( + tsJestTransformOptions: Omit = {}, +): JsWithTsPreset { + logger.debug('creating legacy Js with Ts CJS Jest preset') + + return { + transform: { + [TS_JS_TRANSFORM_PATTERN]: ['ts-jest', tsJestTransformOptions], + }, + } +} + +export function createLegacyWithBabelPreset( + tsJestTransformOptions: Omit = {}, +): JsWithBabelPreset { + logger.debug('creating legacy JS with Babel CJS Jest preset') + + const babelConfig = tsJestTransformOptions.babelConfig + + return { + transform: { + [JS_TRANSFORM_PATTERN]: ['babel-jest', typeof babelConfig === 'object' ? babelConfig : {}], + [TS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + ...tsJestTransformOptions, + babelConfig: true, + }, + ], + }, + } +} + +export function createLegacyDefaultEsmPreset(tsJestTransformOptions: Omit = {}): { + extensionsToTreatAsEsm: string[] + transform: { + [ESM_TS_TRANSFORM_PATTERN]: ['ts-jest', { useESM: true } & typeof tsJestTransformOptions] + } +} { + logger.debug('creating default legacy ESM Jest preset') + + return { + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + [ESM_TS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + ...tsJestTransformOptions, + useESM: true, + }, + ], + }, + } +} + +export function createLegacyJsWithTsEsmPreset(tsJestTransformOptions: Omit = {}): { + extensionsToTreatAsEsm: string[] + transform: { + [ESM_TS_JS_TRANSFORM_PATTERN]: ['ts-jest', { useESM: true } & typeof tsJestTransformOptions] + } +} { + logger.debug('creating Js with Ts legacy ESM Jest preset') + + return { + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + [ESM_TS_JS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + ...tsJestTransformOptions, + useESM: true, + }, + ], + }, + } +} + +export function createLegacyWithBabelEsmPreset(tsJestTransformOptions: Omit = {}): { + extensionsToTreatAsEsm: string[] + transform: { + [ESM_JS_TRANSFORM_PATTERN]: ['babel-jest', babelConfig: BabelConfig] + [ESM_TS_TRANSFORM_PATTERN]: ['ts-jest', { useESM: true } & typeof tsJestTransformOptions] + } +} { + logger.debug('creating JS with Babel legacy ESM Jest preset') + + const babelConfig = tsJestTransformOptions.babelConfig + + return { + extensionsToTreatAsEsm: [...JS_EXT_TO_TREAT_AS_ESM, ...TS_EXT_TO_TREAT_AS_ESM], + transform: { + [ESM_JS_TRANSFORM_PATTERN]: ['babel-jest', typeof babelConfig === 'object' ? babelConfig : {}], + [ESM_TS_TRANSFORM_PATTERN]: [ + 'ts-jest', + { + ...tsJestTransformOptions, + useESM: true, + babelConfig, + }, + ], + }, + } +} diff --git a/src/types.ts b/src/types.ts index 3b1b6ebf24..6ad9f432fe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ import type * as babelJest from 'babel-jest' import type * as _babel from 'babel__core' import type * as _ts from 'typescript' +import { JS_TRANSFORM_PATTERN, TS_JS_TRANSFORM_PATTERN, TS_TRANSFORM_PATTERN } from './constants' import type { ConfigSet } from './legacy/config/config-set' import type { RawCompilerOptions } from './raw-compiler-options' @@ -203,6 +204,9 @@ export interface JestConfigWithTsJest extends Omit] + } +} +export type JsWithTsPreset = { + transform: { + [TS_JS_TRANSFORM_PATTERN]: ['ts-jest', Omit] + } +} +export type JsWithBabelPreset = { + transform: { + [JS_TRANSFORM_PATTERN]: ['babel-jest', babelConfig: BabelConfig] + [TS_TRANSFORM_PATTERN]: ['ts-jest', Omit] + } +} diff --git a/src/utils/messages.ts b/src/utils/messages.ts index b23c35cd9b..7bf2d0e2ca 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -42,7 +42,8 @@ export const enum Deprecations { GlobalsTsJestConfigOption = 'Define `ts-jest` config under `globals` is deprecated. Please do\n' + 'transform: {\n' + " : ['ts-jest', { /* ts-jest config goes here in Jest */ }],\n" + - '},', + '},\n' + + 'See more at https://kulshekhar.github.io/ts-jest/docs/getting-started/presets#advanced', } /**