• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kulshekhar / ts-jest / 4526462444

pending completion
4526462444

push

github

GitHub
build(deps): update dependency typescript to v5 (#4064)

937 of 1054 branches covered (88.9%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

4055 of 4154 relevant lines covered (97.62%)

518.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.02
/src/cli/config/migrate.ts
1
import { existsSync } from 'fs'
3✔
2
import { basename, resolve } from 'path'
3✔
3

3✔
4
import type { Config } from '@jest/types'
3✔
5
import { createLogger } from 'bs-logger'
3✔
6
import stableStringify from 'fast-json-stable-stringify'
3✔
7
import { stringify as stringifyJson5 } from 'json5'
3✔
8

3✔
9
import type { CliCommand, CliCommandArgs } from '..'
3✔
10
import { backportJestConfig } from '../../utils/backports'
3✔
11
import { JestPresetNames, TsJestPresetDescriptor, allPresets, defaults } from '../helpers/presets'
3✔
12

3✔
13
/**
3✔
14
 * @internal
3✔
15
 */
3✔
16
export const run: CliCommand = async (args: CliCommandArgs /* , logger: Logger*/) => {
3✔
17
  const nullLogger = createLogger({ targets: [] })
60✔
18
  const file = args._[0]?.toString()
60!
19
  const filePath = resolve(process.cwd(), file)
60✔
20
  const footNotes: string[] = []
60✔
21
  if (!existsSync(filePath)) {
60✔
22
    throw new Error(`Configuration file ${file} does not exists.`)
3✔
23
  }
3✔
24
  const name = basename(file)
57✔
25
  const isPackage = name === 'package.json'
57✔
26
  if (!/\.(js|json)$/.test(name)) {
60✔
27
    throw new TypeError(`Configuration file ${file} must be a JavaScript or JSON file.`)
3✔
28
  }
3✔
29
  let actualConfig: Config.InitialOptions = require(filePath)
54✔
30
  if (isPackage) {
60✔
31
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
39✔
32
    actualConfig = (actualConfig as any).jest
39✔
33
  }
39✔
34
  if (!actualConfig) actualConfig = {}
60!
35

54✔
36
  // migrate
54✔
37
  // first we backport our options
54✔
38
  const migratedConfig = backportJestConfig(nullLogger, actualConfig)
54✔
39
  let presetName: JestPresetNames | undefined
54✔
40
  let preset: TsJestPresetDescriptor | undefined
54✔
41
  // then we check if we can use `preset`
54✔
42
  if (!migratedConfig.preset && args.jestPreset) {
60✔
43
    // find the best preset
42✔
44
    if (args.js) {
42✔
45
      presetName = args.js === 'babel' ? JestPresetNames.jsWIthBabel : JestPresetNames.jsWithTs
3!
46
    } else {
42✔
47
      // try to detect what transformer the js extensions would target
39✔
48
      const jsTransformers = Object.keys(migratedConfig.transform || {}).reduce((list, pattern) => {
39✔
49
        if (RegExp(pattern.replace(/^<rootDir>\/?/, '/dummy-project/')).test('/dummy-project/src/foo.js')) {
30✔
50
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
12✔
51
          let transformer: string = (migratedConfig.transform as any)[pattern]
12✔
52
          if (/\bbabel-jest\b/.test(transformer)) transformer = 'babel-jest'
12✔
53
          else if (/\ts-jest\b/.test(transformer)) transformer = 'ts-jest'
6!
54

12✔
55
          return [...list, transformer]
12✔
56
        }
12✔
57

18✔
58
        return list
18✔
59
      }, [] as string[])
39✔
60
      // depending on the transformer found, we use one or the other preset
39✔
61
      const jsWithTs = jsTransformers.includes('ts-jest')
39✔
62
      const jsWithBabel = jsTransformers.includes('babel-jest')
39✔
63
      if (jsWithBabel && !jsWithTs) {
39✔
64
        presetName = JestPresetNames.jsWIthBabel
6✔
65
      } else if (jsWithTs && !jsWithBabel) {
39✔
66
        presetName = JestPresetNames.jsWithTs
3✔
67
      } else {
33✔
68
        // sounds like js files are NOT handled, or handled with a unknown transformer, so we do not need to handle it
30✔
69
        presetName = JestPresetNames.default
30✔
70
      }
30✔
71
    }
39✔
72
    // ensure we are using a preset
42✔
73
    presetName = presetName ?? JestPresetNames.default
42!
74
    preset = allPresets[presetName]
42✔
75
    footNotes.push(
42✔
76
      `Detected preset '${preset.label}' as the best matching preset for your configuration.
42✔
77
Visit https://kulshekhar.github.io/ts-jest/user/config/#jest-preset for more information about presets.
42✔
78
`,
42✔
79
    )
42✔
80
  } else if (migratedConfig.preset?.startsWith('ts-jest')) {
60✔
81
    if (args.jestPreset === false) {
6!
82
      delete migratedConfig.preset
×
83
    } else {
6✔
84
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
6✔
85
      preset = (allPresets as any)[migratedConfig.preset] ?? defaults
6!
86
    }
6✔
87
  }
6✔
88

54✔
89
  // enforce the correct name
54✔
90
  if (preset) migratedConfig.preset = preset.name
60✔
91

54✔
92
  // check the extensions
54✔
93
  if (migratedConfig.moduleFileExtensions?.length && preset) {
60✔
94
    const presetValue = dedupSort(preset.value.moduleFileExtensions ?? []).join('::')
3!
95
    const migratedValue = dedupSort(migratedConfig.moduleFileExtensions).join('::')
3✔
96
    if (presetValue === migratedValue) {
3!
97
      delete migratedConfig.moduleFileExtensions
×
98
    }
×
99
  }
3✔
100
  // there is a testRegex, remove our testMatch
54✔
101
  if ((typeof migratedConfig.testRegex === 'string' || migratedConfig.testRegex?.length) && preset) {
60✔
102
    delete migratedConfig.testMatch
9✔
103
  }
9✔
104
  // check the testMatch
45✔
105
  else if (migratedConfig.testMatch?.length && preset) {
45✔
106
    const presetValue = dedupSort(preset.value.testMatch ?? []).join('::')
9!
107
    const migratedValue = dedupSort(migratedConfig.testMatch).join('::')
9✔
108
    if (presetValue === migratedValue) {
9!
109
      delete migratedConfig.testMatch
×
110
    }
×
111
  }
9✔
112

54✔
113
  // migrate the transform
54✔
114
  if (migratedConfig.transform) {
60✔
115
    Object.keys(migratedConfig.transform).forEach((key) => {
15✔
116
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
30✔
117
      const val = (migratedConfig.transform as any)[key]
30✔
118
      if (typeof val === 'string' && /\/?ts-jest?(?:\/preprocessor\.js)?$/.test(val)) {
30✔
119
        // eslint-disable-next-line
21✔
120
        ;(migratedConfig.transform as any)[key] = 'ts-jest'
21✔
121
      }
21✔
122
    })
15✔
123
  }
15✔
124

54✔
125
  // migrate globals config to transformer config
54✔
126
  const globalsTsJestConfig = migratedConfig.globals?.['ts-jest']
60!
127
  if (globalsTsJestConfig && migratedConfig.transform) {
60✔
128
    Object.keys(migratedConfig.transform).forEach((key) => {
15✔
129
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
30✔
130
      const val = (migratedConfig.transform as any)[key]
30✔
131
      if (typeof val === 'string' && val.includes('ts-jest')) {
30✔
132
        // eslint-disable-next-line
21✔
133
        ;(migratedConfig.transform as any)[key] = Object.keys(globalsTsJestConfig).length ? [val, globalsTsJestConfig] : val
21✔
134
      }
21✔
135
    })
15✔
136
    delete (migratedConfig.globals ?? Object.create(null))['ts-jest']
15!
137
  }
15✔
138

54✔
139
  // check if it's the same as the preset's one
54✔
140
  if (preset && migratedConfig.transform) {
60✔
141
    if (stableStringify(migratedConfig.transform) === stableStringify(preset.value.transform)) {
15!
142
      delete migratedConfig.transform
×
143
    } else {
15✔
144
      const migratedConfigTransform = migratedConfig.transform
15✔
145
      const presetValueTransform = preset.value.transform
15✔
146
      if (migratedConfigTransform && presetValueTransform) {
15✔
147
        migratedConfig.transform = Object.entries(migratedConfigTransform).reduce(
15✔
148
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
15✔
149
          (acc: undefined | Record<string, any>, [fileRegex, transformerConfig]) => {
15✔
150
            const presetValueTransformerConfig = presetValueTransform[fileRegex]
30✔
151
            const shouldRemoveDuplicatedConfig =
30✔
152
              (presetValueTransformerConfig &&
30✔
153
                Array.isArray(presetValueTransformerConfig) &&
30✔
154
                transformerConfig === presetValueTransformerConfig[0] &&
30✔
155
                !Object.keys(presetValueTransformerConfig[1]).length) ||
30✔
156
              transformerConfig === presetValueTransformerConfig
24✔
157

30✔
158
            return shouldRemoveDuplicatedConfig
30✔
159
              ? acc
30✔
160
              : acc
30✔
161
              ? { ...acc, [fileRegex]: transformerConfig }
18✔
162
              : { [fileRegex]: transformerConfig }
18✔
163
          },
15✔
164
          undefined,
15✔
165
        )
15✔
166
      }
15✔
167
    }
15✔
168
  }
15✔
169

54✔
170
  // cleanup
54✔
171
  cleanupConfig(actualConfig)
54✔
172
  cleanupConfig(migratedConfig)
54✔
173
  const before = stableStringify(actualConfig)
54✔
174
  const after = stableStringify(migratedConfig)
54✔
175
  if (after === before) {
60✔
176
    process.stderr.write(`
6✔
177
No migration needed for given Jest configuration
6✔
178
    `)
6✔
179

6✔
180
    return
6✔
181
  }
6✔
182

48✔
183
  const stringify = file.endsWith('.json') ? JSON.stringify : stringifyJson5
60✔
184
  const prefix = file.endsWith('.json') ? '"jest": ' : 'module.exports = '
60✔
185

60✔
186
  // if we are using preset, inform the user that he might be able to remove some section(s)
60✔
187
  // we couldn't check for equality
60✔
188
  //   if (usesPreset && migratedConfig.testMatch) {
60✔
189
  //     footNotes.push(`
60✔
190
  // I couldn't check if your "testMatch" value is the same as mine which is: ${stringify(
60✔
191
  //       presets.testMatch,
60✔
192
  //       undefined,
60✔
193
  //       '  ',
60✔
194
  //     )}
60✔
195
  // If it is the case, you can safely remove the "testMatch" from what I've migrated.
60✔
196
  // `)
60✔
197
  //   }
60✔
198
  if (preset && migratedConfig.transform) {
60✔
199
    footNotes.push(`
9✔
200
I couldn't check if your "transform" value is the same as mine which is: ${stringify(
9✔
201
      preset.value.transform,
9✔
202
      undefined,
9✔
203
      '  ',
9✔
204
    )}
9✔
205
If it is the case, you can safely remove the "transform" from what I've migrated.
9✔
206
`)
9✔
207
  }
9✔
208

48✔
209
  // output new config
48✔
210
  process.stderr.write(`
48✔
211
Migrated Jest configuration:
48✔
212
`)
48✔
213
  process.stdout.write(`${prefix}${stringify(migratedConfig, undefined, '  ')}\n`)
48✔
214
  if (footNotes.length) {
60✔
215
    process.stderr.write(`
42✔
216
${footNotes.join('\n')}
42✔
217
`)
42✔
218
  }
42✔
219
}
60✔
220

3✔
221
function cleanupConfig(config: Config.InitialOptions): void {
108✔
222
  if (config.globals) {
108✔
223
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
66✔
224
    if ((config as any).globals['ts-jest'] && Object.keys((config as any).globals['ts-jest']).length === 0) {
66✔
225
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
30✔
226
      delete (config as any).globals['ts-jest']
30✔
227
    }
30✔
228
    if (!Object.keys(config.globals).length) {
66✔
229
      delete config.globals
54✔
230
    }
54✔
231
  }
66✔
232
  if (config.transform && !Object.keys(config.transform).length) {
108!
233
    delete config.transform
×
234
  }
×
235
  if (config.moduleFileExtensions) {
108✔
236
    config.moduleFileExtensions = dedupSort(config.moduleFileExtensions)
6✔
237
    if (!config.moduleFileExtensions.length) delete config.moduleFileExtensions
6!
238
  }
6✔
239
  if (config.testMatch) {
108✔
240
    config.testMatch = dedupSort(config.testMatch)
24✔
241
    if (!config.testMatch.length) delete config.testMatch
24!
242
  }
24✔
243
  if (config.preset === JestPresetNames.default) config.preset = defaults.name
108!
244
}
108✔
245

3✔
246
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3✔
247
function dedupSort(arr: any[]) {
54✔
248
  return arr
54✔
249
    .filter((s, i, a) => a.findIndex((e) => s.toString() === e.toString()) === i)
54✔
250
    .sort((a, b) => (a.toString() > b.toString() ? 1 : a.toString() < b.toString() ? -1 : 0))
54!
251
}
54✔
252

3✔
253
/**
3✔
254
 * @internal
3✔
255
 */
3✔
256
export const help: CliCommand = async () => {
3✔
257
  process.stdout.write(`
3✔
258
Usage:
3✔
259
  ts-jest config:migrate [options] <config-file>
3✔
260

3✔
261
Arguments:
3✔
262
  <config-file>         Can be a js or json Jest config file. If it is a
3✔
263
                        package.json file, the configuration will be read from
3✔
264
                        the "jest" property.
3✔
265

3✔
266
Options:
3✔
267
  --js ts|babel         Process .js files with ts-jest if 'ts' or with
3✔
268
                        babel-jest if 'babel'
3✔
269
  --no-jest-preset      Disable the use of Jest presets
3✔
270
`)
3✔
271
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc