1- import { Tree , writeJson , readJson } from '@nx/devkit' ;
1+ import { Tree , writeJson } from '@nx/devkit' ;
22import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing' ;
33import migration from './convert-jest-config-to-cjs' ;
44
@@ -7,10 +7,6 @@ describe('convert-jest-config-to-cjs', () => {
77
88 beforeEach ( ( ) => {
99 tree = createTreeWithEmptyWorkspace ( ) ;
10- // Register @nx /jest/plugin in nx.json - required for migration to run
11- const nxJson = readJson ( tree , 'nx.json' ) ;
12- nxJson . plugins = [ '@nx/jest/plugin' ] ;
13- writeJson ( tree , 'nx.json' , nxJson ) ;
1410 } ) ;
1511
1612 describe ( 'export default conversion' , ( ) => {
@@ -183,6 +179,178 @@ export default {
183179 } ) ;
184180 } ) ;
185181
182+ describe ( 'type-only imports' , ( ) => {
183+ it ( 'should leave `import type { X } from "mod"` untouched' , async ( ) => {
184+ tree . write (
185+ 'apps/app1/jest.config.ts' ,
186+ `import type { Config } from 'jest';
187+
188+ const config: Config = {
189+ displayName: 'app1',
190+ };
191+
192+ export default config;`
193+ ) ;
194+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
195+
196+ await migration ( tree ) ;
197+
198+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
199+ expect ( content ) . toMatchInlineSnapshot ( `
200+ "import type { Config } from 'jest';
201+
202+ const config: Config = {
203+ displayName: 'app1',
204+ };
205+
206+ module.exports = config;
207+ "
208+ ` ) ;
209+ } ) ;
210+
211+ it ( 'should leave `import type Default from "mod"` untouched' , async ( ) => {
212+ tree . write (
213+ 'apps/app1/jest.config.ts' ,
214+ `import type Config from 'jest';
215+
216+ const config: Config = { displayName: 'app1' };
217+
218+ export default config;`
219+ ) ;
220+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
221+
222+ await migration ( tree ) ;
223+
224+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
225+ expect ( content ) . toMatchInlineSnapshot ( `
226+ "import type Config from 'jest';
227+
228+ const config: Config = { displayName: 'app1' };
229+
230+ module.exports = config;
231+ "
232+ ` ) ;
233+ } ) ;
234+
235+ it ( 'should leave `import type * as T from "mod"` untouched' , async ( ) => {
236+ tree . write (
237+ 'apps/app1/jest.config.ts' ,
238+ `import type * as Jest from 'jest';
239+
240+ const config: Jest.Config = { displayName: 'app1' };
241+
242+ export default config;`
243+ ) ;
244+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
245+
246+ await migration ( tree ) ;
247+
248+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
249+ expect ( content ) . toMatchInlineSnapshot ( `
250+ "import type * as Jest from 'jest';
251+
252+ const config: Jest.Config = { displayName: 'app1' };
253+
254+ module.exports = config;
255+ "
256+ ` ) ;
257+ } ) ;
258+
259+ it ( 'should split inline type specifiers into a type-only import and a require for value specifiers' , async ( ) => {
260+ tree . write (
261+ 'apps/app1/jest.config.ts' ,
262+ `import { type Config, readConfig } from 'some-pkg';
263+
264+ const config: Config = readConfig();
265+ export default config;`
266+ ) ;
267+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
268+
269+ await migration ( tree ) ;
270+
271+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
272+ expect ( content ) . toMatchInlineSnapshot ( `
273+ "import type { Config } from 'some-pkg';
274+ const { readConfig } = require('some-pkg');
275+
276+ const config: Config = readConfig();
277+ module.exports = config;
278+ "
279+ ` ) ;
280+ } ) ;
281+
282+ it ( 'should drop an inline type specifier entirely when it is the only named import' , async ( ) => {
283+ tree . write (
284+ 'apps/app1/jest.config.ts' ,
285+ `import { type Config } from 'jest';
286+
287+ const config: Config = { displayName: 'app1' };
288+ export default config;`
289+ ) ;
290+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
291+
292+ await migration ( tree ) ;
293+
294+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
295+ expect ( content ) . toMatchInlineSnapshot ( `
296+ "import type { Config } from 'jest';
297+
298+ const config: Config = { displayName: 'app1' };
299+ module.exports = config;
300+ "
301+ ` ) ;
302+ } ) ;
303+
304+ it ( 'should split a default + inline type specifiers into three parts' , async ( ) => {
305+ tree . write (
306+ 'apps/app1/jest.config.ts' ,
307+ `import setup, { type Config, helper } from 'some-pkg';
308+
309+ setup();
310+ const config: Config = helper();
311+ export default config;`
312+ ) ;
313+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
314+
315+ await migration ( tree ) ;
316+
317+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
318+ expect ( content ) . toMatchInlineSnapshot ( `
319+ "import type { Config } from 'some-pkg';
320+ const setup = require('some-pkg').default ?? require('some-pkg');
321+ const { helper } = require('some-pkg');
322+
323+ setup();
324+ const config: Config = helper();
325+ module.exports = config;
326+ "
327+ ` ) ;
328+ } ) ;
329+
330+ it ( 'should preserve renamed inline type specifiers' , async ( ) => {
331+ tree . write (
332+ 'apps/app1/jest.config.ts' ,
333+ `import { type Config as JestConfig, run } from 'some-pkg';
334+
335+ const config: JestConfig = run();
336+ export default config;`
337+ ) ;
338+ writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
339+
340+ await migration ( tree ) ;
341+
342+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
343+ expect ( content ) . toMatchInlineSnapshot ( `
344+ "import type { Config as JestConfig } from 'some-pkg';
345+ const { run } = require('some-pkg');
346+
347+ const config: JestConfig = run();
348+ module.exports = config;
349+ "
350+ ` ) ;
351+ } ) ;
352+ } ) ;
353+
186354 describe ( 'ESM module type' , ( ) => {
187355 it ( 'should NOT convert jest.config.ts when project package.json has type: module' , async ( ) => {
188356 const originalContent = `import { readFileSync } from 'fs';
@@ -451,40 +619,22 @@ export default {
451619 } ) ;
452620 } ) ;
453621
454- describe ( 'plugin registration guard' , ( ) => {
455- it ( 'should NOT run migration when @nx/jest/plugin is not registered' , async ( ) => {
456- // Remove the plugin from nx.json
457- const nxJson = readJson ( tree , 'nx.json' ) ;
458- nxJson . plugins = [ ] ;
459- writeJson ( tree , 'nx.json' , nxJson ) ;
460-
461- const originalContent = `export default {
462- displayName: 'app1',
463- };` ;
464- tree . write ( 'apps/app1/jest.config.ts' , originalContent ) ;
465- writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
466-
467- await migration ( tree ) ;
468-
469- // File should remain unchanged
470- const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
471- expect ( content ) . toMatchInlineSnapshot ( `
472- "export default {
473- displayName: 'app1',
474- };"
475- ` ) ;
476- } ) ;
477-
478- it ( 'should run migration when @nx/jest/plugin is registered as object' , async ( ) => {
479- // Register plugin as object format
480- const nxJson = readJson ( tree , 'nx.json' ) ;
481- nxJson . plugins = [ { plugin : '@nx/jest/plugin' , options : { } } ] ;
482- writeJson ( tree , 'nx.json' , nxJson ) ;
483-
622+ describe ( 'executor-based setups' , ( ) => {
623+ it ( 'should convert jest.config.ts even when @nx/jest/plugin is not registered' , async ( ) => {
624+ // Simulate an executor-based workspace: no @nx/jest/plugin in nx.json.
484625 tree . write (
485626 'apps/app1/jest.config.ts' ,
486- `export default {
627+ `import { readFileSync } from 'fs';
628+
629+ const swcJestConfig = JSON.parse(
630+ readFileSync(\`\${__dirname}/.swcrc\`, 'utf-8')
631+ );
632+
633+ export default {
487634 displayName: 'app1',
635+ transform: {
636+ '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
637+ },
488638};`
489639 ) ;
490640 writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
@@ -493,98 +643,23 @@ export default {
493643
494644 const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
495645 expect ( content ) . toMatchInlineSnapshot ( `
496- "module.exports = {
497- displayName: 'app1',
498- };
499- "
500- ` ) ;
501- } ) ;
502-
503- it ( 'should NOT run migration when nx.json does not exist' , async ( ) => {
504- tree . delete ( 'nx.json' ) ;
505-
506- const originalContent = `export default {
507- displayName: 'app1',
508- };` ;
509- tree . write ( 'apps/app1/jest.config.ts' , originalContent ) ;
510- writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
646+ "const { readFileSync } = require('fs');
511647
512- await migration ( tree ) ;
648+ const swcJestConfig = JSON.parse(readFileSync(\`\${__dirname}/.swcrc\`, 'utf-8') );
513649
514- // File should remain unchanged
515- const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
516- expect ( content ) . toMatchInlineSnapshot ( `
517- "export default {
650+ module.exports = {
518651 displayName: 'app1',
519- };"
520- ` ) ;
521- } ) ;
522-
523- it ( 'should NOT convert files excluded from plugin via exclude pattern' , async ( ) => {
524- // Register plugin with exclude pattern
525- const nxJson = readJson ( tree , 'nx.json' ) ;
526- nxJson . plugins = [
527- {
528- plugin : '@nx/jest/plugin' ,
529- exclude : [ 'apps/excluded/**/*' ] ,
530- } ,
531- ] ;
532- writeJson ( tree , 'nx.json' , nxJson ) ;
533-
534- const originalContent = `export default {
535- displayName: 'excluded-app',
536- };` ;
537- tree . write ( 'apps/excluded/jest.config.ts' , originalContent ) ;
538- writeJson ( tree , 'apps/excluded/package.json' , { type : 'commonjs' } ) ;
539-
540- // Also create a non-excluded file to ensure it still gets converted
541- tree . write (
542- 'apps/included/jest.config.ts' ,
543- `export default {
544- displayName: 'included-app',
545- };`
546- ) ;
547- writeJson ( tree , 'apps/included/package.json' , { type : 'commonjs' } ) ;
548-
549- await migration ( tree ) ;
550-
551- // Excluded file should remain unchanged
552- const excludedContent = tree . read (
553- 'apps/excluded/jest.config.ts' ,
554- 'utf-8'
555- ) ;
556- expect ( excludedContent ) . toMatchInlineSnapshot ( `
557- "export default {
558- displayName: 'excluded-app',
559- };
560- "
561- ` ) ;
562-
563- // Included file should be converted
564- const includedContent = tree . read (
565- 'apps/included/jest.config.ts' ,
566- 'utf-8'
567- ) ;
568- expect ( includedContent ) . toMatchInlineSnapshot ( `
569- "module.exports = {
570- displayName: 'included-app',
652+ transform: {
653+ '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
654+ },
571655 };
572656 "
573657 ` ) ;
574658 } ) ;
575659
576- it ( 'should only convert files matching include pattern' , async ( ) => {
577- // Register plugin with include pattern
578- const nxJson = readJson ( tree , 'nx.json' ) ;
579- nxJson . plugins = [
580- {
581- plugin : '@nx/jest/plugin' ,
582- include : [ 'libs/**/*' ] ,
583- } ,
584- ] ;
585- writeJson ( tree , 'nx.json' , nxJson ) ;
586-
587- // Create file outside include pattern
660+ it ( 'should convert jest.config.ts when nx.json does not exist' , async ( ) => {
661+ tree . delete ( 'nx.json' ) ;
662+
588663 tree . write (
589664 'apps/app1/jest.config.ts' ,
590665 `export default {
@@ -593,31 +668,12 @@ export default {
593668 ) ;
594669 writeJson ( tree , 'apps/app1/package.json' , { type : 'commonjs' } ) ;
595670
596- // Create file inside include pattern
597- tree . write (
598- 'libs/lib1/jest.config.ts' ,
599- `export default {
600- displayName: 'lib1',
601- };`
602- ) ;
603- writeJson ( tree , 'libs/lib1/package.json' , { type : 'commonjs' } ) ;
604-
605671 await migration ( tree ) ;
606672
607- // File outside include pattern should remain unchanged
608- const appContent = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
609- expect ( appContent ) . toMatchInlineSnapshot ( `
610- "export default {
611- displayName: 'app1',
612- };
613- "
614- ` ) ;
615-
616- // File inside include pattern should be converted
617- const libContent = tree . read ( 'libs/lib1/jest.config.ts' , 'utf-8' ) ;
618- expect ( libContent ) . toMatchInlineSnapshot ( `
673+ const content = tree . read ( 'apps/app1/jest.config.ts' , 'utf-8' ) ;
674+ expect ( content ) . toMatchInlineSnapshot ( `
619675 "module.exports = {
620- displayName: 'lib1 ',
676+ displayName: 'app1 ',
621677 };
622678 "
623679 ` ) ;
0 commit comments