@@ -53,6 +53,7 @@ interface ReactArguments extends BaseArguments {
53
53
bundler : 'webpack' | 'vite' | 'rspack' ;
54
54
nextAppDir : boolean ;
55
55
nextSrcDir : boolean ;
56
+ unitTestRunner : 'none' | 'jest' | 'vitest' ;
56
57
e2eTestRunner : 'none' | 'cypress' | 'playwright' ;
57
58
}
58
59
@@ -63,6 +64,7 @@ interface AngularArguments extends BaseArguments {
63
64
style : string ;
64
65
routing : boolean ;
65
66
standaloneApi : boolean ;
67
+ unitTestRunner : 'none' | 'jest' | 'vitest' ;
66
68
e2eTestRunner : 'none' | 'cypress' | 'playwright' ;
67
69
bundler : 'webpack' | 'esbuild' ;
68
70
ssr : boolean ;
@@ -76,15 +78,17 @@ interface VueArguments extends BaseArguments {
76
78
appName : string ;
77
79
framework : 'none' | 'nuxt' ;
78
80
style : string ;
81
+ unitTestRunner : 'none' | 'vitest' ;
79
82
e2eTestRunner : 'none' | 'cypress' | 'playwright' ;
80
83
}
81
84
82
85
interface NodeArguments extends BaseArguments {
83
86
stack : 'node' ;
84
87
workspaceType : 'standalone' | 'integrated' ;
85
88
appName : string ;
86
- framework : 'express' | 'fastify' | 'koa' | 'nest' ;
89
+ framework : 'none' | ' express' | 'fastify' | 'koa' | 'nest' ;
87
90
docker : boolean ;
91
+ unitTestRunner : 'none' | 'jest' ;
88
92
}
89
93
90
94
interface UnknownStackArguments extends BaseArguments {
@@ -190,6 +194,11 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
190
194
choices : [ 'playwright' , 'cypress' , 'none' ] ,
191
195
type : 'string' ,
192
196
} )
197
+ . option ( 'unitTestRunner' , {
198
+ describe : chalk . dim `Test runner to use for unit tests.` ,
199
+ choices : [ 'jest' , 'vitest' , 'none' ] ,
200
+ type : 'string' ,
201
+ } )
193
202
. option ( 'ssr' , {
194
203
describe : chalk . dim `Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.` ,
195
204
type : 'boolean' ,
@@ -568,11 +577,12 @@ async function determineNoneOptions(
568
577
569
578
async function determineReactOptions (
570
579
parsedArgs : yargs . Arguments < ReactArguments >
571
- ) : Promise < Partial < Arguments > > {
580
+ ) : Promise < Partial < ReactArguments > > {
572
581
let preset : Preset ;
573
582
let style : undefined | string = undefined ;
574
583
let appName : string ;
575
584
let bundler : undefined | 'webpack' | 'vite' | 'rspack' = undefined ;
585
+ let unitTestRunner : undefined | 'none' | 'jest' | 'vitest' = undefined ;
576
586
let e2eTestRunner : undefined | 'none' | 'cypress' | 'playwright' = undefined ;
577
587
let nextAppDir = false ;
578
588
let nextSrcDir = false ;
@@ -633,17 +643,29 @@ async function determineReactOptions(
633
643
634
644
if ( preset === Preset . ReactStandalone || preset === Preset . ReactMonorepo ) {
635
645
bundler = await determineReactBundler ( parsedArgs ) ;
646
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
647
+ preferVitest : bundler === 'vite' ,
648
+ } ) ;
636
649
e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
637
650
} else if ( preset === Preset . NextJs || preset === Preset . NextJsStandalone ) {
638
651
nextAppDir = await determineNextAppDir ( parsedArgs ) ;
639
652
nextSrcDir = await determineNextSrcDir ( parsedArgs ) ;
653
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
654
+ exclude : 'vitest' ,
655
+ } ) ;
640
656
e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
641
657
} else if (
642
658
preset === Preset . RemixMonorepo ||
643
- preset === Preset . RemixStandalone ||
644
- preset === Preset . ReactNative ||
645
- preset === Preset . Expo
659
+ preset === Preset . RemixStandalone
646
660
) {
661
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
662
+ preferVitest : true ,
663
+ } ) ;
664
+ e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
665
+ } else if ( preset === Preset . ReactNative || preset === Preset . Expo ) {
666
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
667
+ exclude : 'vitest' ,
668
+ } ) ;
647
669
e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
648
670
}
649
671
@@ -715,6 +737,7 @@ async function determineReactOptions(
715
737
bundler,
716
738
nextAppDir,
717
739
nextSrcDir,
740
+ unitTestRunner,
718
741
e2eTestRunner,
719
742
linter,
720
743
formatter,
@@ -724,10 +747,11 @@ async function determineReactOptions(
724
747
725
748
async function determineVueOptions (
726
749
parsedArgs : yargs . Arguments < VueArguments >
727
- ) : Promise < Partial < Arguments > > {
750
+ ) : Promise < Partial < VueArguments > > {
728
751
let preset : Preset ;
729
752
let style : undefined | string = undefined ;
730
753
let appName : string ;
754
+ let unitTestRunner : undefined | 'none' | 'vitest' = undefined ;
731
755
let e2eTestRunner : undefined | 'none' | 'cypress' | 'playwright' = undefined ;
732
756
let linter : undefined | 'none' | 'eslint' ;
733
757
let formatter : undefined | 'none' | 'prettier' ;
@@ -768,6 +792,9 @@ async function determineVueOptions(
768
792
}
769
793
}
770
794
795
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
796
+ exclude : 'jest' ,
797
+ } ) ;
771
798
e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
772
799
773
800
if ( parsedArgs . style ) {
@@ -815,6 +842,7 @@ async function determineVueOptions(
815
842
preset,
816
843
style,
817
844
appName,
845
+ unitTestRunner,
818
846
e2eTestRunner,
819
847
linter,
820
848
formatter,
@@ -824,10 +852,11 @@ async function determineVueOptions(
824
852
825
853
async function determineAngularOptions (
826
854
parsedArgs : yargs . Arguments < AngularArguments >
827
- ) : Promise < Partial < Arguments > > {
855
+ ) : Promise < Partial < AngularArguments > > {
828
856
let preset : Preset ;
829
857
let style : string ;
830
858
let appName : string ;
859
+ let unitTestRunner : undefined | 'none' | 'jest' | 'vitest' = undefined ;
831
860
let e2eTestRunner : undefined | 'none' | 'cypress' | 'playwright' = undefined ;
832
861
let bundler : undefined | 'webpack' | 'esbuild' = undefined ;
833
862
let ssr : undefined | boolean = undefined ;
@@ -965,6 +994,7 @@ async function determineAngularOptions(
965
994
serverRouting = false ;
966
995
}
967
996
997
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs ) ;
968
998
e2eTestRunner = await determineE2eTestRunner ( parsedArgs ) ;
969
999
970
1000
return {
@@ -973,6 +1003,7 @@ async function determineAngularOptions(
973
1003
appName,
974
1004
standaloneApi,
975
1005
routing,
1006
+ unitTestRunner,
976
1007
e2eTestRunner,
977
1008
bundler,
978
1009
ssr,
@@ -983,14 +1014,14 @@ async function determineAngularOptions(
983
1014
984
1015
async function determineNodeOptions (
985
1016
parsedArgs : yargs . Arguments < NodeArguments >
986
- ) : Promise < Partial < Arguments > > {
1017
+ ) : Promise < Partial < NodeArguments > > {
987
1018
let preset : Preset ;
988
1019
let appName : string ;
989
1020
let framework : 'express' | 'fastify' | 'koa' | 'nest' | 'none' ;
990
1021
let docker : boolean ;
991
1022
let linter : undefined | 'none' | 'eslint' ;
992
1023
let formatter : undefined | 'none' | 'prettier' ;
993
-
1024
+ let unitTestRunner : undefined | 'none' | 'jest' = undefined ;
994
1025
const workspaces = parsedArgs . workspaces ?? false ;
995
1026
996
1027
if ( parsedArgs . preset ) {
@@ -1051,6 +1082,10 @@ async function determineNodeOptions(
1051
1082
docker = reply . docker === 'Yes' ;
1052
1083
}
1053
1084
1085
+ unitTestRunner = await determineUnitTestRunner ( parsedArgs , {
1086
+ exclude : 'vitest' ,
1087
+ } ) ;
1088
+
1054
1089
if ( workspaces ) {
1055
1090
linter = await determineLinterOptions ( parsedArgs ) ;
1056
1091
formatter = await determineFormatterOptions ( parsedArgs ) ;
@@ -1067,6 +1102,7 @@ async function determineNodeOptions(
1067
1102
linter,
1068
1103
formatter,
1069
1104
workspaces,
1105
+ unitTestRunner,
1070
1106
} ;
1071
1107
}
1072
1108
@@ -1358,6 +1394,60 @@ async function determineNodeFramework(
1358
1394
return reply . framework ;
1359
1395
}
1360
1396
1397
+ async function determineUnitTestRunner < T extends 'none' | 'jest' | 'vitest' > (
1398
+ parsedArgs : yargs . Arguments < {
1399
+ bundler ?: 'vite' | string ;
1400
+ unitTestRunner ?: T ;
1401
+ workspaces ?: boolean ;
1402
+ } > ,
1403
+ options ?: {
1404
+ exclude ?: 'jest' | 'vitest' ;
1405
+ preferVitest ?: boolean ;
1406
+ }
1407
+ ) : Promise < T | undefined > {
1408
+ if ( parsedArgs . unitTestRunner ) {
1409
+ return parsedArgs . unitTestRunner ;
1410
+ } else if ( ! parsedArgs . workspaces ) {
1411
+ return undefined ;
1412
+ }
1413
+
1414
+ const reply = await enquirer . prompt < {
1415
+ unitTestRunner : 'none' | 'jest' | 'vitest' ;
1416
+ } > ( [
1417
+ {
1418
+ message : 'Which unit test runner would you like to use?' ,
1419
+ type : 'autocomplete' ,
1420
+ name : 'unitTestRunner' ,
1421
+ skip : ! parsedArgs . interactive || isCI ( ) ,
1422
+ choices : [
1423
+ {
1424
+ name : 'none' ,
1425
+ message : 'None' ,
1426
+ } ,
1427
+ {
1428
+ name : 'jest' ,
1429
+ message : 'Jest [ https://jestjs.io/ ]' ,
1430
+ } ,
1431
+ {
1432
+ name : 'vitest' ,
1433
+ message : 'Vitest [ https://vitest.dev/ ]' ,
1434
+ } ,
1435
+ ]
1436
+ . filter ( ( t ) => ! options ?. exclude || options . exclude !== t . name )
1437
+ . sort ( ( a , b ) => {
1438
+ if ( a . name === 'none' ) return - 1 ;
1439
+ if ( b . name === 'none' ) return 1 ;
1440
+ if ( options ?. preferVitest && a . name === 'vitest' ) return - 1 ;
1441
+ if ( options ?. preferVitest && b . name === 'vitest' ) return 1 ;
1442
+ return 0 ;
1443
+ } ) ,
1444
+ initial : 0 ,
1445
+ } ,
1446
+ ] ) ;
1447
+
1448
+ return reply . unitTestRunner as T ;
1449
+ }
1450
+
1361
1451
async function determineE2eTestRunner (
1362
1452
parsedArgs : yargs . Arguments < {
1363
1453
e2eTestRunner ?: 'none' | 'cypress' | 'playwright' ;
0 commit comments