@@ -27,8 +27,9 @@ async function processTestTargetOptions(
2727 customBuildOptions : Record < string , Record < string , json . JsonValue | undefined > > ,
2828 needDevkitPlugin : boolean ,
2929 manualMigrationFiles : string [ ] ,
30- ) : Promise < boolean > {
30+ ) : Promise < { needsCoverage : boolean ; needsIstanbul : boolean } > {
3131 let needsCoverage = false ;
32+ let needsIstanbul = false ;
3233 for ( const [ configName , options ] of allTargetOptions ( testTarget , false ) ) {
3334 const configKey = configName || '' ;
3435 if ( ! customBuildOptions [ configKey ] ) {
@@ -81,6 +82,20 @@ async function processTestTargetOptions(
8182
8283 const updatedBrowsers = options [ 'browsers' ] ;
8384 if ( Array . isArray ( updatedBrowsers ) && updatedBrowsers . length > 0 ) {
85+ const hasNonChromium = updatedBrowsers . some ( ( b ) => {
86+ if ( typeof b !== 'string' ) {
87+ return false ;
88+ }
89+
90+ const normalized = b . toLowerCase ( ) ;
91+
92+ return ! [ 'chrome' , 'chromium' , 'edge' ] . some ( ( name ) => normalized . includes ( name ) ) ;
93+ } ) ;
94+
95+ if ( hasNonChromium ) {
96+ needsIstanbul = true ;
97+ }
98+
8499 context . logger . info (
85100 `Project "${ projectName } " has browsers configured for tests. ` +
86101 `To run tests in a browser with Vitest, you will need to install either ` +
@@ -118,7 +133,7 @@ async function processTestTargetOptions(
118133 delete options [ 'main' ] ;
119134 }
120135
121- return needsCoverage ;
136+ return { needsCoverage, needsIstanbul } ;
122137}
123138
124139function updateTsConfigTypes (
@@ -154,9 +169,49 @@ function updateTsConfigTypes(
154169 }
155170}
156171
172+ function logSummary (
173+ context : SchematicContext ,
174+ migratedProjects : string [ ] ,
175+ skippedNonApplications : string [ ] ,
176+ skippedMissingAppBuilder : string [ ] ,
177+ manualMigrationFiles : string [ ] ,
178+ ) : void {
179+ context . logger . info ( '\n--- Karma to Vitest Migration Summary ---' ) ;
180+ context . logger . info ( `Projects migrated: ${ migratedProjects . length } ` ) ;
181+ if ( migratedProjects . length > 0 ) {
182+ context . logger . info ( ` - ${ migratedProjects . join ( ', ' ) } ` ) ;
183+ }
184+ context . logger . info ( `Projects skipped (non-applications): ${ skippedNonApplications . length } ` ) ;
185+ if ( skippedNonApplications . length > 0 ) {
186+ context . logger . info ( ` - ${ skippedNonApplications . join ( ', ' ) } ` ) ;
187+ }
188+ context . logger . info (
189+ `Projects skipped (missing application builder): ${ skippedMissingAppBuilder . length } ` ,
190+ ) ;
191+ if ( skippedMissingAppBuilder . length > 0 ) {
192+ context . logger . info ( ` - ${ skippedMissingAppBuilder . join ( ', ' ) } ` ) ;
193+ }
194+
195+ const uniqueManualFiles = [ ...new Set ( manualMigrationFiles ) ] ;
196+ if ( uniqueManualFiles . length > 0 ) {
197+ context . logger . warn ( `\nThe following Karma configuration files require manual migration:` ) ;
198+ for ( const file of uniqueManualFiles ) {
199+ context . logger . warn ( ` - ${ file } ` ) ;
200+ }
201+ }
202+ if ( migratedProjects . length > 0 ) {
203+ context . logger . info (
204+ `\nNote: To refactor your test files from Jasmine to Vitest, consider running the following command:` +
205+ `\n ng g @schematics/angular:refactor-jasmine-vitest <project_name>` ,
206+ ) ;
207+ }
208+ context . logger . info ( '-----------------------------------------\n' ) ;
209+ }
210+
157211function updateProjects ( tree : Tree , context : SchematicContext ) : Rule {
158212 return updateWorkspace ( async ( workspace ) => {
159213 let needsCoverage = false ;
214+ let needsIstanbul = false ;
160215 const removableKarmaConfigs = new Map < string , KarmaConfigProcessingResult > ( ) ;
161216 const migratedProjects : string [ ] = [ ] ;
162217 const tsConfigsToUpdate = new Set < string > ( ) ;
@@ -223,7 +278,7 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule {
223278 // Store custom build options to move to a new build configuration if needed
224279 const customBuildOptions : Record < string , Record < string , json . JsonValue | undefined > > = { } ;
225280
226- const projectNeedsCoverage = await processTestTargetOptions (
281+ const projectCoverageInfo = await processTestTargetOptions (
227282 testTarget ,
228283 projectName ,
229284 context ,
@@ -234,8 +289,11 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule {
234289 manualMigrationFiles ,
235290 ) ;
236291
237- if ( projectNeedsCoverage ) {
292+ if ( projectCoverageInfo . needsCoverage ) {
238293 needsCoverage = true ;
294+ if ( projectCoverageInfo . needsIstanbul ) {
295+ needsIstanbul = true ;
296+ }
239297 }
240298
241299 // If we have custom build options, create testing configurations
@@ -297,36 +355,13 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule {
297355 updateTsConfigTypes ( tree , tsConfigsToUpdate , context ) ;
298356
299357 // Log summary
300- context . logger . info ( '\n--- Karma to Vitest Migration Summary ---' ) ;
301- context . logger . info ( `Projects migrated: ${ migratedProjects . length } ` ) ;
302- if ( migratedProjects . length > 0 ) {
303- context . logger . info ( ` - ${ migratedProjects . join ( ', ' ) } ` ) ;
304- }
305- context . logger . info ( `Projects skipped (non-applications): ${ skippedNonApplications . length } ` ) ;
306- if ( skippedNonApplications . length > 0 ) {
307- context . logger . info ( ` - ${ skippedNonApplications . join ( ', ' ) } ` ) ;
308- }
309- context . logger . info (
310- `Projects skipped (missing application builder): ${ skippedMissingAppBuilder . length } ` ,
358+ logSummary (
359+ context ,
360+ migratedProjects ,
361+ skippedNonApplications ,
362+ skippedMissingAppBuilder ,
363+ manualMigrationFiles ,
311364 ) ;
312- if ( skippedMissingAppBuilder . length > 0 ) {
313- context . logger . info ( ` - ${ skippedMissingAppBuilder . join ( ', ' ) } ` ) ;
314- }
315-
316- const uniqueManualFiles = [ ...new Set ( manualMigrationFiles ) ] ;
317- if ( uniqueManualFiles . length > 0 ) {
318- context . logger . warn ( `\nThe following Karma configuration files require manual migration:` ) ;
319- for ( const file of uniqueManualFiles ) {
320- context . logger . warn ( ` - ${ file } ` ) ;
321- }
322- }
323- if ( migratedProjects . length > 0 ) {
324- context . logger . info (
325- `\nNote: To refactor your test files from Jasmine to Vitest, consider running the following command:` +
326- `\n ng g @schematics/angular:refactor-jasmine-vitest <project_name>` ,
327- ) ;
328- }
329- context . logger . info ( '-----------------------------------------\n' ) ;
330365
331366 if ( migratedProjects . length > 0 ) {
332367 const rules = [
@@ -343,6 +378,19 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule {
343378 existing : ExistingBehavior . Skip ,
344379 } ) ,
345380 ) ;
381+
382+ if ( needsIstanbul ) {
383+ rules . push (
384+ addDependency (
385+ '@vitest/coverage-istanbul' ,
386+ latestVersions [ '@vitest/coverage-istanbul' ] ,
387+ {
388+ type : DependencyType . Dev ,
389+ existing : ExistingBehavior . Skip ,
390+ } ,
391+ ) ,
392+ ) ;
393+ }
346394 }
347395
348396 return chain ( rules ) ;
0 commit comments