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:
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()inmain.ts, nozone.jsin thepolyfillsarray) 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 virtualangular:test-bed-initmodule: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:
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'becausezone.jsis transitively installed. The virtualangular:test-bed-initmodule contains a top-levelawait, 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
Anything else relevant?
No response