66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9+ /**
10+ * @fileoverview
11+ * Provides Vitest-specific build options and virtual file contents for Angular unit testing.
12+ */
13+
914import { createRequire } from 'node:module' ;
1015import path from 'node:path' ;
1116import { toPosixPath } from '../../../../utils/path' ;
@@ -29,6 +34,17 @@ function createTestBedInitVirtualFile(
2934 providersImport = `import providers from './${ importPath } ';` ;
3035 }
3136
37+ let zoneTestingSnippet = '' ;
38+ if ( zoneTestingStrategy === 'static' ) {
39+ zoneTestingSnippet = `import 'zone.js/testing';` ;
40+ } else if ( zoneTestingStrategy === 'dynamic' ) {
41+ zoneTestingSnippet = `if (typeof Zone !== 'undefined') {
42+ // 'zone.js/testing' is used to initialize the ZoneJS testing environment.
43+ // It must be imported dynamically to avoid a static dependency on 'zone.js'.
44+ await import('zone.js/testing');
45+ }` ;
46+ }
47+
3248 return `
3349 // Initialize the Angular testing environment
3450 import { NgModule, provideZoneChangeDetection } from '@angular/core';
@@ -37,18 +53,7 @@ function createTestBedInitVirtualFile(
3753 import { afterEach, beforeEach } from 'vitest';
3854 ${ providersImport }
3955
40- ${
41- zoneTestingStrategy === 'static'
42- ? `import 'zone.js/testing';`
43- : zoneTestingStrategy === 'dynamic'
44- ? `
45- if (typeof Zone !== 'undefined') {
46- // 'zone.js/testing' is used to initialize the ZoneJS testing environment.
47- // It must be imported dynamically to avoid a static dependency on 'zone.js'.
48- await import('zone.js/testing');
49- }`
50- : ''
51- }
56+ ${ zoneTestingSnippet }
5257
5358 // The beforeEach and afterEach hooks are registered outside the globalThis guard.
5459 // This ensures that the hooks are always applied, even in non-isolated browser environments.
@@ -92,6 +97,28 @@ function adjustOutputHashing(hashing?: OutputHashing): OutputHashing {
9297 }
9398}
9499
100+ function getZoneTestingStrategy (
101+ buildOptions : Partial < ApplicationBuilderInternalOptions > ,
102+ projectSourceRoot : string ,
103+ ) : 'none' | 'static' | 'dynamic' {
104+ if ( buildOptions . polyfills ?. includes ( 'zone.js/testing' ) ) {
105+ return 'none' ;
106+ }
107+
108+ if ( buildOptions . polyfills ?. includes ( 'zone.js' ) ) {
109+ return 'static' ;
110+ }
111+
112+ try {
113+ const projectRequire = createRequire ( path . join ( projectSourceRoot , 'package.json' ) ) ;
114+ projectRequire . resolve ( 'zone.js' ) ;
115+
116+ return 'dynamic' ;
117+ } catch {
118+ return 'none' ;
119+ }
120+ }
121+
95122export async function getVitestBuildOptions (
96123 options : NormalizedUnitTestBuilderOptions ,
97124 baseBuildOptions : Partial < ApplicationBuilderInternalOptions > ,
@@ -162,20 +189,7 @@ export async function getVitestBuildOptions(
162189 } ;
163190
164191 // Inject the zone.js testing polyfill if Zone.js is installed.
165- let zoneTestingStrategy : 'none' | 'static' | 'dynamic' ;
166- if ( buildOptions . polyfills ?. includes ( 'zone.js/testing' ) ) {
167- zoneTestingStrategy = 'none' ;
168- } else if ( buildOptions . polyfills ?. includes ( 'zone.js' ) ) {
169- zoneTestingStrategy = 'static' ;
170- } else {
171- try {
172- const projectRequire = createRequire ( path . join ( projectSourceRoot , 'package.json' ) ) ;
173- projectRequire . resolve ( 'zone.js' ) ;
174- zoneTestingStrategy = 'dynamic' ;
175- } catch {
176- zoneTestingStrategy = 'none' ;
177- }
178- }
192+ const zoneTestingStrategy = getZoneTestingStrategy ( buildOptions , projectSourceRoot ) ;
179193
180194 const testBedInitContents = createTestBedInitVirtualFile (
181195 providersFile ,
0 commit comments