Command
test
Is this a regression?
- Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
21.2.13
Description
Description
A zoneless Angular project (provideZonelessChangeDetection() in main.ts, no zone.js in the polyfills array) fails to compile its unit-test bundle with:
X [ERROR] Top-level await is not available in the configured target environment ("chrome145.0", "ios17.0", "safari17.0" + 2 overrides)
Root cause
In @angular/build/src/builders/unit-test/runners/vitest/build-options.js, getZoneTestingStrategy() decides what to emit into the virtual angular:test-bed-init module:
function getZoneTestingStrategy(buildOptions, projectSourceRoot) {
if (buildOptions.polyfills?.includes('zone.js/testing')) return 'none';
if (buildOptions.polyfills?.includes('zone.js')) return 'static';
try {
projectRequire.resolve('zone.js');
return 'dynamic'; // ← falls through to here for zoneless projects
} catch { return 'none'; }
}
For a zoneless app, polyfills correctly contains neither 'zone.js' nor 'zone.js/testing' — but zone.js is still resolvable on disk because it's an optional/peer dep of @angular/core (and several Angular test utilities like fakeAsync / tick still reference it). The strategy therefore resolves to 'dynamic', which emits this into the virtual module:
if (typeof Zone !== 'undefined') {
// 'zone.js/testing' is used to initialize the ZoneJS testing environment.
// It must be imported dynamically to avoid a static dependency on 'zone.js'.
await import('zone.js/testing');
}
The runtime guard typeof Zone !== 'undefined' would correctly skip the import on a zoneless app, but esbuild evaluates the top-level await at compile time and rejects the syntax — regardless of whether the branch can actually run. The build fails before the guard ever executes.
Expected
A zoneless project should resolve the strategy to 'none' and emit no zone-init code. The unit-test bundle should compile cleanly.
Actual
Strategy resolves to 'dynamic' because zone.js is transitively installed. The virtual angular:test-bed-init module contains a top-level await, which esbuild rejects against the configured browser targets — even though those targets (Chrome 145, iOS 17, Safari 17) actually do support TLA. The compile-time rejection appears to be tied to the output format guarantees rather than the targets themselves, so tightening browserslist does not help.
Minimal Reproduction
- Generate a zoneless Angular 22 app (or upgrade an existing zoneless app).
- Confirm main.ts uses provideZonelessChangeDetection() and polyfills in angular.json does not include 'zone.js' or 'zone.js/testing'.
- Configure test to use @angular/build:unit-test with the vitest runner.
- Run ng test.
Exception or Error
Top-level await is not available
Your Environment
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI : 22.0.0
Angular : 22.0.0
Node.js : 24.16.0
Package Manager : npm 11.13.0
Operating System : win32 x64
┌────────────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├────────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/build-angular │ 22.0.0 │ 22.0.0 │
│ @angular/animations │ 22.0.0 │ 22.0.0 │
│ @angular/build │ 22.0.0 │ 22.0.0 │
│ @angular/cdk │ 21.2.13 │ 21.2.13 │
│ @angular/cli │ 22.0.0 │ 22.0.0 │
│ @angular/common │ 22.0.0 │ 22.0.0 │
│ @angular/compiler │ 22.0.0 │ 22.0.0 │
│ @angular/compiler-cli │ 22.0.0 │ 22.0.0 │
│ @angular/core │ 22.0.0 │ 22.0.0 │
│ @angular/forms │ 22.0.0 │ 22.0.0 │
│ @angular/google-maps │ 21.2.13 │ 21.2.13 │
│ @angular/language-service │ 22.0.0 │ 22.0.0 │
│ @angular/material │ 21.2.13 │ 21.2.13 │
│ @angular/material-date-fns-adapter │ 21.2.13 │ 21.2.13 │
│ @angular/platform-browser │ 22.0.0 │ 22.0.0 │
│ @angular/platform-browser-dynamic │ 22.0.0 │ 22.0.0 │
│ @angular/platform-server │ 22.0.0 │ 22.0.0 │
│ @angular/router │ 22.0.0 │ 22.0.0 │
│ @angular/service-worker │ 22.0.0 │ 22.0.0 │
│ @angular/ssr │ 22.0.0 │ 22.0.0 │
│ rxjs │ 7.8.2 │ 7.8.2 │
│ typescript │ 6.0.3 │ 6.0.3 │
│ vitest │ 4.1.7 │ 4.1.7 │
Anything else relevant?
No response
Command
test
Is this a regression?
The previous version in which this bug was not present was
21.2.13
Description
Description
A zoneless Angular project (provideZonelessChangeDetection() in main.ts, no zone.js in the polyfills array) fails to compile its unit-test bundle with:
X [ERROR] Top-level await is not available in the configured target environment ("chrome145.0", "ios17.0", "safari17.0" + 2 overrides)
Root cause
In @angular/build/src/builders/unit-test/runners/vitest/build-options.js, getZoneTestingStrategy() decides what to emit into the virtual angular:test-bed-init module:
For a zoneless app, polyfills correctly contains neither 'zone.js' nor 'zone.js/testing' — but zone.js is still resolvable on disk because it's an optional/peer dep of @angular/core (and several Angular test utilities like fakeAsync / tick still reference it). The strategy therefore resolves to 'dynamic', which emits this into the virtual module:
if (typeof Zone !== 'undefined') { // 'zone.js/testing' is used to initialize the ZoneJS testing environment. // It must be imported dynamically to avoid a static dependency on 'zone.js'. await import('zone.js/testing'); }The runtime guard typeof Zone !== 'undefined' would correctly skip the import on a zoneless app, but esbuild evaluates the top-level await at compile time and rejects the syntax — regardless of whether the branch can actually run. The build fails before the guard ever executes.
Expected
A zoneless project should resolve the strategy to 'none' and emit no zone-init code. The unit-test bundle should compile cleanly.
Actual
Strategy resolves to 'dynamic' because zone.js is transitively installed. The virtual angular:test-bed-init module contains a top-level await, which esbuild rejects against the configured browser targets — even though those targets (Chrome 145, iOS 17, Safari 17) actually do support TLA. The compile-time rejection appears to be tied to the output format guarantees rather than the targets themselves, so tightening browserslist does not help.
Minimal Reproduction
Exception or Error
Your Environment
_ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI : 22.0.0 Angular : 22.0.0 Node.js : 24.16.0 Package Manager : npm 11.13.0 Operating System : win32 x64 ┌────────────────────────────────────┬───────────────────┬───────────────────┐ │ Package │ Installed Version │ Requested Version │ ├────────────────────────────────────┼───────────────────┼───────────────────┤ │ @angular-devkit/build-angular │ 22.0.0 │ 22.0.0 │ │ @angular/animations │ 22.0.0 │ 22.0.0 │ │ @angular/build │ 22.0.0 │ 22.0.0 │ │ @angular/cdk │ 21.2.13 │ 21.2.13 │ │ @angular/cli │ 22.0.0 │ 22.0.0 │ │ @angular/common │ 22.0.0 │ 22.0.0 │ │ @angular/compiler │ 22.0.0 │ 22.0.0 │ │ @angular/compiler-cli │ 22.0.0 │ 22.0.0 │ │ @angular/core │ 22.0.0 │ 22.0.0 │ │ @angular/forms │ 22.0.0 │ 22.0.0 │ │ @angular/google-maps │ 21.2.13 │ 21.2.13 │ │ @angular/language-service │ 22.0.0 │ 22.0.0 │ │ @angular/material │ 21.2.13 │ 21.2.13 │ │ @angular/material-date-fns-adapter │ 21.2.13 │ 21.2.13 │ │ @angular/platform-browser │ 22.0.0 │ 22.0.0 │ │ @angular/platform-browser-dynamic │ 22.0.0 │ 22.0.0 │ │ @angular/platform-server │ 22.0.0 │ 22.0.0 │ │ @angular/router │ 22.0.0 │ 22.0.0 │ │ @angular/service-worker │ 22.0.0 │ 22.0.0 │ │ @angular/ssr │ 22.0.0 │ 22.0.0 │ │ rxjs │ 7.8.2 │ 7.8.2 │ │ typescript │ 6.0.3 │ 6.0.3 │ │ vitest │ 4.1.7 │ 4.1.7 │Anything else relevant?
No response