GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `@angular/build:unit-test`: with the default jsdom runner and zone.js, an export from a shared chunk is `undefined` when read during class-field initialisation from a spec with an async test · Issue #33728 · angular/angular-cli

@angular/build:unit-test: with the default jsdom runner and zone.js, an export from a shared chunk is undefined when read during class-field initialisation from a spec with an async test #33728

New issue
Copy link
New issue
Copy link
Closed
#33729
Closed
@angular/build:unit-test: with the default jsdom runner and zone.js, an export from a shared chunk is undefined when read during class-field initialisation from a spec with an async test#33728
#33729
Copy link
Labels
area: @angular/buildgemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini
Milestone
needsTriage

Description

@jonmarozick
jonmarozick
opened on Aug 2, 2026
Issue body actions

Command

test

Is this a regression?

Not known to be — this is the @angular/build:unit-test builder's default (jsdom) runner; I have no earlier version where this worked.

Description

Under the unit-test builder's default jsdom runner, a module that esbuild places in a chunk shared between spec entry points is wrapped in a lazy __esm initialiser, and a consumer in a different chunk reads its exports as undefined during class-field initialisation. The same specs pass when the runner is switched to browser mode with the same bundles, so this looks like the Node-side module evaluation path rather than a bundling fault.

The distinction that makes it precise:

  • a service reads the shared const inside a method → always correct;
  • a component reads the same const in a class-field initialiser → undefined.

The binding is live — replacing the fields with getters (reading the same import later) makes the failing test pass with no other change. So the value arrives; at construction time it is not yet visible to the importer, even though the emitted code calls init_env_config() earlier in the same module body and construction happens later still.

Trigger conditions — each toggled on its own with everything else constant; all four are required:

# Required condition Evidence
1 ≥ 2 spec entries import the module (shared chunk, lazy __esm wrappers) a single spec file inlines everything and passes
2 a consumer in another chunk reads the export during class-field initialisation getters pass
3 the consuming spec file contains an async test callback making that one test synchronous passes; async with no await still fails
4 zone.js is in the unit-test polyfills deleting "polyfills": ["zone.js"] from the unit-test build configuration makes the same suite pass; restoring it fails again

Not required (removed with the failure surviving): TestBed.configureTestingModule() / compileComponents() (a bare TestBed.createComponent() inside an async test fails), provider overrides, barrel files, @angular/core patch level (fails on 21.2.18 and 21.2.19).

Conditions 3 and 4 are one mechanism: with zone.js present the builder downlevels async/await, so a spec containing any async function imports the __async helper — and esbuild then emits that spec entry CommonJS-wrapped, with its entry-local ESM dependencies (the component module) as lazy __esm initialisers:

// spec-…-lead-generator.container.js (dumped with ng test --dump-virtual-files)
import { DEALERS, LeadService, init_env_config, init_lead_service } from "./chunk-N64URYIK.js";
import { __async, __commonJS, __esm } from "./chunk-EFV3TZQO.js";

var LeadGeneratorContainer;
var init_lead_generator_container = __esm({
  "…/lead-generator.container.ts"() {
    init_services();
    init_env_config();                       // runs before the class statement below
    LeadGeneratorContainer = class _LeadGeneratorContainer {
      leadService = inject(LeadService);
      dealers = DEALERS;                     // undefined at construction time
      dealerOptions = this.dealers.map(…);   // throws
    };
  }
});

var require_lead_generator_container_spec = __commonJS({
  "…/lead-generator.container.spec.ts"(exports) {
    init_lead_generator_container();
    describe("LeadGeneratorContainer", () => { … });
  }
});
export default require_lead_generator_container_spec();

The shared chunk assigns DEALERS inside init_env_config()'s __esm wrapper and exports the binding; the emitted code is valid ESM and runs correctly in browser mode.

One caveat for anyone building a paraphrased fixture: the failure is sensitive to inert content — adding a top-level console.log to either the shared or the importing module defuses it, and one hand-written structural clone of the same shape passes. The linked reproduction is the exact shape that fails deterministically.

Minimal Reproduction

https://github.com/jonmarozick/ng-shared-chunk-repro

npm install
npm test        # ng test --no-watch

Expected: 2 passed. Actual: Test Files 1 failed | 1 passed (2) — lead-generator.container.spec.ts fails, lead.service.spec.ts (the control reading the same const in a method) passes.

Nine source files, dependencies are only Angular 21.2.19 + Vitest 4 + jsdom, package-lock.json committed. The module graph:

src/environments/env-config.ts        exports `const DEALERS`
  ├── lead.service.ts                 reads DEALERS in a method
  │     ├── lead.service.spec.ts      ← spec entry 1 (passes)
  │     └── lead-generator.container.ts
  └── lead-generator.container.ts     reads DEALERS in a field initialiser
        └── lead-generator.container.spec.ts   ← spec entry 2 (fails)

Also reproduced by copying these source files unchanged into a fresh ng new workspace on CLI 21.2.19 (@angular/core 21.2.19) with "polyfills": ["zone.js"] added to the unit-test build configuration — so nothing about it depends on the original project. The emitted bundles can be inspected via ng test --dump-virtual-files (they land in .angular/cache/<version>/<project>/unit-test/output-files/).

Exception or Error

TypeError: Cannot read properties of undefined (reading 'map')
 ❯ _LeadGeneratorContainer.<instance_members_initializer> src/app/features/lead-generator/lead-generator.container.ts:291:43
 ❯ new _LeadGeneratorContainer src/app/features/lead-generator/lead-generator.container.ts:336:5
 ❯ NodeInjectorFactory.LeadGeneratorContainer_Factory [as factory] spec-app-features-lead-generator-lead-generator.container.js:1138:16
 ❯ getNodeInjectable packages/core/src/render3/di.ts:785:37
 ❯ instantiateAllDirectives packages/core/src/render3/instructions/shared.ts:122:2
 ❯ createComponentRef packages/core/src/render3/component_ref.ts:301:20
 ❯ create packages/core/testing/src/test_bed.ts:695:44

Your Environment

Angular CLI: 21.2.19
Node: 24.18.0
Package Manager: npm 11.16.0
OS: win32 x64

@angular/build         21.2.19
@angular/cli           21.2.19
@angular/core          21.2.18
@angular/compiler-cli  21.2.18
typescript             5.9.3
vitest                 4.1.10
zone.js                0.16.2
jsdom                  (builder default)

Also reproduced on Linux CI (ubuntu-latest, Node 24) and in a fresh ng new workspace with @angular/core 21.2.19.

Anything else relevant?

  • Not avoidable through runner configuration: isolate true/false, pool forks/threads/vmThreads, fileParallelism: false, and dropping runnerConfig entirely all still fail. Removing zone.js avoids it but is not available to a zone-based app, and "don't write async tests" is not a workaround.
  • The impact can be silent: in a sibling project on the same setup, jsdom reported 108 of 216 tests (three spec files never loaded) in half the wall-clock of browser mode — a faster jsdom run can mean less of the suite ran.
  • Not tested on 22.x (project fleet is pinned to v21 LTS); the two vitest-related 22.x changelog entries don't touch the module-interop path.
  • Possibly related open issues, none the same: "No test suite found in file" error when running Vitest #32252, @angular/build:unit-test virtual init-testbed.js guards initTestEnvironment() behind a once-per-worker symbol → stale DomAdapter under vitest ≥4.0.5 + isolate: false #33047, unit-test (vitest): isolate:false default silently breaks cross-file vi.mock() of shared modules on low-CPU workers #33570.
  • I have a verified fix (disabling code splitting for the unit-test build only, mirroring the polyfills-bundle precedent) and will open a PR referencing this issue.

Activity

  1. angular-robot
    added
    area: @angular/build
    gemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini
    on Aug 2, 2026
  2. angular-robot
    added this to the needsTriage milestone on Aug 2, 2026
  3. jonmarozick
    added a commit that references this issue on Aug 2, 2026

    Minimise the failing spec, and document the exact trigger set

    1b0b28f
  4. dgp1130
    closed this as completedin #33729on Aug 13, 2026
  5. alan-agius4
    added 9 commits that reference this issue on Aug 27, 2026

    fix(@angular/build): re-enable code splitting for unit tests

    8bce16a

    fix(@angular/build): re-enable code splitting for unit tests

    cfaa501

    fix(@angular/build): re-enable code splitting for unit tests

    2c03d17

    fix(@angular/build): re-enable code splitting for unit tests

    f0f034f

    fix(@angular/build): re-enable code splitting for unit tests

    3dd7aa1

    fix(@angular/build): re-enable code splitting for unit tests

    c3cefba

    fix(@angular/build): re-enable code splitting for unit tests

    78d8f1c

    fix(@angular/build): re-enable code splitting for unit tests

    ba0a2f0

    fix(@angular/build): re-enable code splitting for unit tests

    18757a9
  6. reikla
    added a commit that references this issue on Sep 2, 2026

    AB#5075 Chore: libraries hygiene — class-field getters, editorconfig …

    80e7fde
  7. angular-automatic-lock-bot commented on Oct 19, 2026

    @angular-automatic-lock-bot
    angular-automatic-lock-botBot
    on Oct 19, 2026
    More actions

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  8. angular-automatic-lock-bot
    locked and limited conversation to collaborators on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: @angular/buildgemini-triagedLabel noting that an issue has been triaged by geminiLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

      Milestone

      • needsTriageNo due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.