Skip to content

Commit

Permalink
feat(presets): introduce util functions to create presets
Browse files Browse the repository at this point in the history
This change will allow users to easily extend existing presets to override certain options
  • Loading branch information
ahnpnl committed Jun 27, 2024
1 parent 9c616f9 commit f9cc3c0
Show file tree
Hide file tree
Showing 9 changed files with 568 additions and 188 deletions.
191 changes: 92 additions & 99 deletions src/cli/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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')
})
Expand Down Expand Up @@ -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({
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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 <file> 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', () => {
Expand Down
70 changes: 38 additions & 32 deletions src/cli/config/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 <file> 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'
`)
}
10 changes: 9 additions & 1 deletion src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -11,7 +12,14 @@ const logger = rootLogger.child({ [LogContexts.namespace]: 'cli', [LogContexts.a
/**
* @internal
*/
export type CliCommandArgs = Omit<Arguments, '$0'> & { _: Array<string | number> }
export type CliCommandArgs = Omit<Arguments, '$0'> & { _: Array<string | number> } & {
jestPreset?: boolean
force?: boolean
tsconfig?: TsJestTransformerOptions['tsconfig']
babel?: boolean
jsdom?: boolean
js?: 'ts' | 'babel'
}
/**
* @internal
*/
Expand Down
Loading

0 comments on commit f9cc3c0

Please sign in to comment.