Skip to content

Commit f9cc3c0

Browse files
committed
feat(presets): introduce util functions to create presets
This change will allow users to easily extend existing presets to override certain options
1 parent 9c616f9 commit f9cc3c0

9 files changed

+568
-188
lines changed

src/cli/cli.spec.ts

+92-99
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,21 @@ describe('config', () => {
117117
'--tsconfig',
118118
'tsconfig.test.json',
119119
'--jsdom',
120-
'--no-jest-preset',
120+
'--jest-preset',
121121
'--js',
122122
'ts',
123123
'--babel',
124124
]
125125

126-
it('should create a jest.config.json (without options)', async () => {
126+
it('should create a jest.config.js (without options)', async () => {
127127
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
128-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
129-
fs.readFileSync.mockImplementation((f): any => {
130-
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
131-
throw new Error('ENOENT')
132-
})
133-
expect.assertions(2)
128+
fs.readFileSync
129+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
130+
.mockImplementationOnce((f): any => {
131+
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
132+
throw new Error('ENOENT')
133+
})
134+
expect.assertions(3)
134135
const res = await runCli(...noOption)
135136

136137
expect(res).toEqual({
@@ -141,26 +142,26 @@ Jest configuration written to "${normalize('/foo/bar/jest.config.js')}".
141142
`,
142143
stdout: '',
143144
})
144-
expect(fs.writeFileSync.mock.calls).toEqual([
145-
[
146-
normalize('/foo/bar/jest.config.js'),
147-
`/** @type {import('ts-jest').JestConfigWithTsJest} */
148-
module.exports = {
149-
preset: 'ts-jest',
150-
testEnvironment: 'node',
151-
};`,
152-
],
153-
])
145+
expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.js'))
146+
expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(`
147+
"/** @type {import('ts-jest').JestConfigWithTsJest} **/
148+
module.exports = {
149+
testEnvironment: 'node',
150+
transform: {
151+
'^.+.tsx?$': 'ts-jest',
152+
},
153+
};"
154+
`)
154155
})
155156

156-
it('should create a jest.config.foo.json (with all options set)', async () => {
157+
it('should create a jest.config.foo.js (with all options set)', async () => {
157158
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
158159
// eslint-disable-next-line @typescript-eslint/no-explicit-any
159-
fs.readFileSync.mockImplementation((f): any => {
160+
fs.readFileSync.mockImplementationOnce((f): any => {
160161
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
161162
throw new Error('ENOENT')
162163
})
163-
expect.assertions(2)
164+
expect.assertions(3)
164165
const res = await runCli(...fullOptions, 'jest.config.foo.js')
165166

166167
expect(res).toEqual({
@@ -171,29 +172,60 @@ Jest configuration written to "${normalize('/foo/bar/jest.config.foo.js')}".
171172
`,
172173
stdout: '',
173174
})
174-
expect(fs.writeFileSync.mock.calls).toEqual([
175-
[
176-
normalize('/foo/bar/jest.config.foo.js'),
177-
`const { jsWithTs: tsjPreset } = require('ts-jest/presets');
178-
179-
/** @type {import('ts-jest').JestConfigWithTsJest} */
180-
module.exports = {
181-
...tsjPreset,
182-
transform: {
183-
'^.+\\\\.[tj]sx?$': ['ts-jest', {
184-
tsconfig: 'tsconfig.test.json',
185-
babelConfig: true,
186-
}],
187-
},
188-
};`,
189-
],
190-
])
175+
expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.foo.js'))
176+
expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(`
177+
"/** @type {import('ts-jest').JestConfigWithTsJest} **/
178+
module.exports = {
179+
testEnvironment: 'jsdom',
180+
transform: {
181+
'^.+.[tj]sx?$':
182+
[
183+
'ts-jest',
184+
{
185+
tsconfig: 'tsconfig.test.json'
186+
}
187+
]
188+
,
189+
},
190+
};"
191+
`)
192+
})
193+
194+
it('should create jest config with type "module" package.json', async () => {
195+
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
196+
fs.readFileSync
197+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
198+
.mockImplementationOnce((f): any => {
199+
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0', type: 'module' })
200+
throw new Error('ENOENT')
201+
})
202+
expect.assertions(3)
203+
const res = await runCli(...noOption)
204+
205+
expect(res).toEqual({
206+
exitCode: 0,
207+
log: '',
208+
stderr: `
209+
Jest configuration written to "${normalize('/foo/bar/jest.config.js')}".
210+
`,
211+
stdout: '',
212+
})
213+
expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/jest.config.js'))
214+
expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(`
215+
"/** @type {import('ts-jest').JestConfigWithTsJest} **/
216+
export default {
217+
testEnvironment: 'node',
218+
transform: {
219+
'^.+.tsx?$': 'ts-jest',
220+
},
221+
};"
222+
`)
191223
})
192224

193225
it('should update package.json (without options)', async () => {
194226
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
195227
// eslint-disable-next-line @typescript-eslint/no-explicit-any
196-
fs.readFileSync.mockImplementation((f): any => {
228+
fs.readFileSync.mockImplementationOnce((f): any => {
197229
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
198230
throw new Error('ENOENT')
199231
})
@@ -225,12 +257,13 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}".
225257

226258
it('should update package.json (with all options set)', async () => {
227259
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
228-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
229-
fs.readFileSync.mockImplementation((f): any => {
230-
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
231-
throw new Error('ENOENT')
232-
})
233-
expect.assertions(2)
260+
fs.readFileSync
261+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
262+
.mockImplementationOnce((f): any => {
263+
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0' })
264+
throw new Error('ENOENT')
265+
})
266+
expect.assertions(3)
234267
const res = await runCli(...fullOptions, 'package.json')
235268

236269
expect(res).toEqual({
@@ -241,26 +274,16 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}".
241274
`,
242275
stdout: '',
243276
})
244-
expect(fs.writeFileSync.mock.calls).toEqual([
245-
[
246-
normalize('/foo/bar/package.json'),
247-
`{
248-
"name": "mock",
249-
"version": "0.0.0-mock.0",
250-
"jest": {
251-
"transform": {
252-
"^.+\\\\.[tj]sx?$": [
253-
"ts-jest",
254-
{
255-
"tsconfig": "tsconfig.test.json",
256-
"babelConfig": true
257-
}
258-
]
259-
}
260-
}
261-
}`,
262-
],
263-
])
277+
expect(fs.writeFileSync.mock.calls[0][0]).toBe(normalize('/foo/bar/package.json'))
278+
expect(fs.writeFileSync.mock.calls[0][1]).toMatchInlineSnapshot(`
279+
"{
280+
"name": "mock",
281+
"version": "0.0.0-mock.0",
282+
"jest": {
283+
"preset": "ts-jest/presets/js-with-ts"
284+
}
285+
}"
286+
`)
264287
})
265288

266289
it('should output help', async () => {
@@ -289,46 +312,16 @@ Jest configuration written to "${normalize('/foo/bar/package.json')}".
289312
290313
Options:
291314
--force Discard any existing Jest config
292-
--js ts|babel Process .js files with ts-jest if 'ts' or with
315+
--js ts|babel Process '.js' files with ts-jest if 'ts' or with
293316
babel-jest if 'babel'
294-
--no-jest-preset Disable the use of Jest presets
317+
--jest-preset Toggle using preset
295318
--tsconfig <file> Path to the tsconfig.json file
296-
--babel Pipe babel-jest after ts-jest
297-
--jsdom Use jsdom as test environment instead of node
319+
--babel Enable using Babel to process 'js' resulted content from 'ts-jest' processing
320+
--jsdom Use 'jsdom' as test environment instead of 'node'
298321
",
299322
}
300323
`)
301324
})
302-
303-
it('should create jest config with type "module" package.json', async () => {
304-
fs.existsSync.mockImplementation((f) => f === FAKE_PKG)
305-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
306-
fs.readFileSync.mockImplementation((f): any => {
307-
if (f === FAKE_PKG) return JSON.stringify({ name: 'mock', version: '0.0.0-mock.0', type: 'module' })
308-
throw new Error('ENOENT')
309-
})
310-
expect.assertions(2)
311-
const res = await runCli(...noOption)
312-
313-
expect(res).toEqual({
314-
exitCode: 0,
315-
log: '',
316-
stderr: `
317-
Jest configuration written to "${normalize('/foo/bar/jest.config.js')}".
318-
`,
319-
stdout: '',
320-
})
321-
expect(fs.writeFileSync.mock.calls).toEqual([
322-
[
323-
normalize('/foo/bar/jest.config.js'),
324-
`/** @type {import('ts-jest').JestConfigWithTsJest} */
325-
export default {
326-
preset: 'ts-jest',
327-
testEnvironment: 'node',
328-
};`,
329-
],
330-
])
331-
})
332325
})
333326

334327
describe('migrate', () => {

src/cli/config/init.ts

+38-32
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import { existsSync, readFileSync, writeFileSync } from 'fs'
88
import { basename, join } from 'path'
99

10+
import ejs from 'ejs'
1011
import { stringify as stringifyJson5 } from 'json5'
1112

1213
import type { CliCommand, CliCommandArgs } from '..'
14+
import { JEST_CONFIG_EJS_TEMPLATE, TS_JS_TRANSFORM_PATTERN, TS_TRANSFORM_PATTERN } from '../../constants'
1315
import type { JestConfigWithTsJest, TsJestTransformerOptions } from '../../types'
14-
import { type TsJestPresetDescriptor, defaults, jsWIthBabel, jsWithTs } from '../helpers/presets'
16+
import { type TsJestPresetDescriptor, defaults, jsWIthBabel, jsWithTs, JestPresetNames } from '../helpers/presets'
1517

1618
/**
1719
* @internal
@@ -107,34 +109,38 @@ export const run: CliCommand = async (args: CliCommandArgs /* , logger: Logger *
107109
}
108110
body = JSON.stringify({ ...pkgJson, jest: jestConfig }, undefined, ' ')
109111
} else {
110-
// js config
111-
const content: string[] = []
112-
if (!jestPreset) {
113-
content.push(`${preset.jsImport('tsjPreset')};`, '')
114-
}
115-
content.push(`/** @type {import('ts-jest').JestConfigWithTsJest} */`)
116-
const usesModules = pkgJson.type === 'module'
117-
content.push(usesModules ? 'export default {' : 'module.exports = {')
118-
119-
if (jestPreset) {
120-
content.push(` preset: '${preset.name}',`)
121-
} else {
122-
content.push(' ...tsjPreset,')
123-
}
124-
if (!jsdom) content.push(" testEnvironment: 'node',")
125-
126-
if (tsconfig || shouldPostProcessWithBabel) {
127-
content.push(' transform: {')
128-
content.push(" '^.+\\\\.[tj]sx?$': ['ts-jest', {")
129-
if (tsconfig) content.push(` tsconfig: ${stringifyJson5(tsconfig)},`)
130-
if (shouldPostProcessWithBabel) content.push(' babelConfig: true,')
131-
content.push(' }],')
132-
content.push(' },')
112+
let transformPattern = TS_TRANSFORM_PATTERN
113+
let transformValue = !tsconfig
114+
? `'ts-jest'`
115+
: `
116+
[
117+
'ts-jest',
118+
{
119+
tsconfig: ${stringifyJson5(tsconfig)}
120+
}
121+
]
122+
`
123+
if (preset.name === JestPresetNames.jsWithTs) {
124+
transformPattern = TS_JS_TRANSFORM_PATTERN
125+
} else if (preset.name === JestPresetNames.jsWIthBabel) {
126+
transformValue = !tsconfig
127+
? `'ts-jest'`
128+
: `
129+
[
130+
'ts-jest',
131+
{
132+
babelConfig: true,
133+
tsconfig: ${stringifyJson5(tsconfig)}
134+
}
135+
]
136+
`
133137
}
134-
content.push('};')
135-
136-
// join all together
137-
body = content.join('\n')
138+
body = ejs.render(JEST_CONFIG_EJS_TEMPLATE, {
139+
exportKind: pkgJson.type === 'module' ? 'export default' : 'module.exports =',
140+
testEnvironment: jsdom ? 'jsdom' : 'node',
141+
transformPattern,
142+
transformValue,
143+
})
138144
}
139145

140146
writeFileSync(filePath, body)
@@ -160,11 +166,11 @@ Arguments:
160166
161167
Options:
162168
--force Discard any existing Jest config
163-
--js ts|babel Process .js files with ts-jest if 'ts' or with
169+
--js ts|babel Process '.js' files with ts-jest if 'ts' or with
164170
babel-jest if 'babel'
165-
--no-jest-preset Disable the use of Jest presets
171+
--jest-preset Toggle using preset
166172
--tsconfig <file> Path to the tsconfig.json file
167-
--babel Pipe babel-jest after ts-jest
168-
--jsdom Use jsdom as test environment instead of node
173+
--babel Enable using Babel to process 'js' resulted content from 'ts-jest' processing
174+
--jsdom Use 'jsdom' as test environment instead of 'node'
169175
`)
170176
}

src/cli/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { LogContexts, type Logger } from 'bs-logger'
22
import type { Arguments } from 'yargs'
33
import yargsParser from 'yargs-parser'
44

5+
import type { TsJestTransformerOptions } from '../types'
56
import { rootLogger } from '../utils'
67

78
const VALID_COMMANDS = ['help', 'config:migrate', 'config:init']
@@ -11,7 +12,14 @@ const logger = rootLogger.child({ [LogContexts.namespace]: 'cli', [LogContexts.a
1112
/**
1213
* @internal
1314
*/
14-
export type CliCommandArgs = Omit<Arguments, '$0'> & { _: Array<string | number> }
15+
export type CliCommandArgs = Omit<Arguments, '$0'> & { _: Array<string | number> } & {
16+
jestPreset?: boolean
17+
force?: boolean
18+
tsconfig?: TsJestTransformerOptions['tsconfig']
19+
babel?: boolean
20+
jsdom?: boolean
21+
js?: 'ts' | 'babel'
22+
}
1523
/**
1624
* @internal
1725
*/

0 commit comments

Comments
 (0)