Skip to content

test: drain DOM tasks before MSW shutdown - #6895

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
bolichen97:test/drain-happydom-before-msw-shutdown
Aug 30, 2026
Merged

test: drain DOM tasks before MSW shutdown#6895
iamwhatever merged 1 commit into
kirodotdev:mainfrom
bolichen97:test/drain-happydom-before-msw-shutdown

Conversation

@bolichen97

Copy link
Copy Markdown
Collaborator

Problem / Motivation

The full Vitest suite could finish every assertion and still fail during worker teardown. happy-dom could leave an iframe or resource fetch queued until after MSW closed, producing an unhandled AbortError or a real ECONNREFUSED dial to the test origin. Node 25 workers also exposed process-global experimental Web Storage without a persistence path, emitting a warning for every test file.

Why it matters

These failures move between shards with worker timing and obscure real failures. A global rejection filter would also risk hiding genuine application defects, so the test lifecycle itself needs to be deterministic.

What changed

  • register the happy-dom task drain after the MSW close hook, so Vitest's reverse teardown order drains DOM work while interception is still live
  • begin timer teardown before the drain so late registrations cannot create new work
  • keep shared setup compatible with suites that explicitly use jsdom and have no window.happyDOM
  • remove process-level unhandledRejection suppression for AbortError and ECONNREFUSED
  • replace wall-clock timer assertions with zero-delay FIFO event-loop barriers
  • disable Node 25's unrelated process-global experimental Web Storage in Vitest forks; happy-dom's deterministic window storage remains in use
  • structurally pin the worker and teardown contracts

Global overlap audit

All open PRs were checked for the four changed files. #4913 touches the build-plugin list in vite.config.ts; #5274 touches the coverage exclude list. This PR changes only the Vitest worker execArgv region, so neither contributor's behavior or hunk is replaced.

Tests

  • six focused setup/jsdom suites: 84 passed
  • npm run typecheck
  • changed-test ESLint: 0 errors
  • git diff --check
  • clean merge-tree against current main
  • full-suite reproduction after removing the rejection filter: no teardown ECONNREFUSED or process-global localStorage warning; unrelated deterministic failures found there are being fixed separately rather than hidden here

Screenshot evidence

Why no screenshot: This changes only the Vitest worker environment, shared test teardown, and deterministic test coverage; no rendered component, layout, style, motion, or interaction behavior changes.

Checklist

  • One Conventional Commit
  • No retry, sleep, timeout increase, warning filter, or rejection suppression
  • Existing open-PR changes preserved
  • No secrets or generated artifacts

@bolichen97
bolichen97 requested a review from a team as a code owner August 30, 2026 02:21
@bolichen97
bolichen97 requested a review from pepmach August 30, 2026 02:21
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running labels Aug 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — ✅ PASS

UX-level review of a973d0c07cacd99fa25de0177e4bd5883a1074cf via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: PASS

No user-facing surface is touched — only Vitest teardown ordering, test assertions, and worker flags — so there is no rendered experience to evaluate.

[UX-REVIEWED] a973d0c

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of a973d0c07cacd99fa25de0177e4bd5883a1074cf via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Root-cause fix: reorders teardown so DOM work drains under live interception, correctly replacing a process-level rejection filter that could mask real defects.

Watch

  • waitUntilComplete() has no time bound, and beginTimerTeardown() now runs before the drain — so an in-flight task whose completion depends on a wrapped Node timer (e.g. an MSW handler mid-delay() when the file ends) gets its timer cancelled on the spot and the drain waits until vitest's hook-timeout. The old suppressed-rejection path was silently green there; the new failure is loud but surfaces as an opaque afterAll timeout in shared setup.ts rather than naming the leaking test.

Suggestions

  • Bound the drain (race against a generous timeout) and, on expiry, throw an error reporting that DOM tasks were still pending at teardown — same fail-loud outcome, but attributable instead of a bare hook timeout.

[DESIGN-REVIEWED] a973d0c

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed a973d0c07cacd99fa25de0177e4bd5883a1074cf via the fork AI-review pipeline; updated in place on each push.

Review details

Both candidates fail falsification:

Candidate 1 (--no-experimental-webstorage breaks Node 22.0–22.3): The flag was added in Node 22.4.0, so the negation aborts the worker fork on 22.0–22.3, which the >=22 engines floor nominally permits. But Node 22 only became LTS at 22.11.0; 22.0–22.3 are superseded pre-LTS "Current" builds nobody realistically runs in 2026, and CI runs Node 24. The concrete input reduces to "if a developer were to run 22.0–22.3" — a speculative condition, exactly what the discovery pass itself rated low confidence. Below the bar.

Candidate 2 (removed unhandled-rejection guard): The discovery pass states outright it "could not construct a concrete surviving task" that escapes waitUntilComplete(). Without a concrete input (a) and observable outcome (c), it is ungrounded. Dropped.

No grounded Step 2 findings: the setup refactor, structural-pin string search (afterAll(async () => {\n await settleDOMTasksBeforeMockShutdown(window.happyDOM) and afterAll(() => server.close()) both present, drain registered after close), and the timer-teardown test rewrites all check out.

No findings.

[OPUS-REVIEWED] a973d0c

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed a973d0c07cacd99fa25de0177e4bd5883a1074cf via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] a973d0c

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 30, 2026
@iamwhatever
iamwhatever merged commit c5ef86e into kirodotdev:main Aug 30, 2026
70 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants