Skip to content

test(react): give the JSDOM harness one home - #3908

Merged
kojiwakayama merged 3 commits into
mainfrom
test/shared-jsdom-harness
Aug 21, 2026
Merged

kojiwakayama merged 3 commits into
mainfrom
test/shared-jsdom-harness

Conversation

@kojiwakayama

Copy link
Copy Markdown
Contributor

Description

Twelve UI test files each carried their own copy of the same harness — snapshot globals, install them off a JSDOM window, stub ResizeObserver and matchMedia, stub requestAnimationFrame, restore on teardown. Identical code, twelve times.

That duplication is how one defect ended up in all twelve. The rAF stub is a real timer, and nothing drained the frames still queued at teardown, so a frame React scheduled just before unmount became a pending timer — reported by the op sanitizer as a leak, at suite level, with no step and no stack, on whatever branch happened to be pushing.

Fixing twelve copies (#3890) closed the instances. This closes the source.

12 files changed, 28 insertions(+), 798 deletions(-)
files still stubbing requestAnimationFrame by hand: 0

Design

The helper owns the mechanics — snapshot, install, drain, restore — and deliberately does not decide which globals a test installs. Callers opt in:

installComponentDom(dom)                                  // drawer: no matchMedia
installComponentDom(dom, { matchMedia: true })            // most files
installComponentDom(dom, { matchMedia: true, windowGlobals: ["KeyboardEvent", "FocusEvent"] })

That matters because the twelve were not uniform: eight shared a key set, drawer.conformance deliberately omits matchMedia, and three add keyboard/focus constructors. Installing a superset would have changed behaviour for components that branch on a global's absence. Every file keeps exactly the globals it had.

Why the export is called installComponentDom

Fourteen other test files already define a local function installDomGlobals with a different signature. I named the export that first, added the import broadly, and the suite ran 241 passed / 0 failed while those files had an import shadowing their own declaration — invisible because the test tasks run --no-check.

Reverted and renamed. Worth recording: a green test run does not tell you a test file typechecks in this repo. deno task lint:test-typecheck is the gate that does.

Evidence

lint:test-typecheck   Test typecheck baseline holds: 47 grandfathered files, 0 new.
deno check src/index.ts   clean
src/react/ (--parallel --trace-leaks), 3 runs:
  ok | 241 passed (1839 steps) | 0 failed
  ok | 241 passed (1839 steps) | 0 failed
  ok | 241 passed (1839 steps) | 0 failed

No production code changed.

Related Issue(s)

Tracked internally.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test update

Checklist

  • Every file keeps the exact globals it installed before
  • Typecheck gates run, not just the test suite
  • No production code changed

Twelve UI test files each carried their own copy of the same harness: snapshot
globals, install them off a JSDOM window, stub ResizeObserver and matchMedia,
stub requestAnimationFrame, restore on teardown. Identical code, twelve times.

That is how one defect ended up in all twelve. The rAF stub is a real timer, and
nothing drained the frames still queued at teardown, so a frame React scheduled
just before unmount became a pending timer -- which the op sanitizer reports as
a leak, at suite level, with no step and no stack, on whatever branch happens to
be pushing.

Move the mechanics into one module and have each file call it. The helper owns
snapshot, install, drain and restore. It deliberately does not decide which
globals a test installs: callers opt in to matchMedia and to any extra window
constructors they need, so every file keeps exactly the globals it had and this
change alters no behaviour.

Net 798 lines removed, and no file stubs requestAnimationFrame by hand any more.

The export is named installComponentDom rather than installDomGlobals because
fourteen other test files already define a local function by that name with a
different signature. An import would have shadowed those declarations, and with
--no-check on the test tasks the suite would still have run green.
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e6956748-ccea-4792-9463-087a95bc6ed7

📥 Commits

Reviewing files that changed from the base of the PR and between 23803a6 and 5cb1177.

📒 Files selected for processing (13)
  • src/react/components/ui/adapter/drawer.conformance.test.tsx
  • src/react/components/ui/alert-dialog.test.tsx
  • src/react/components/ui/breadcrumb.test.tsx
  • src/react/components/ui/calendar.test.tsx
  • src/react/components/ui/conformance.test.tsx
  • src/react/components/ui/date-picker.test.tsx
  • src/react/components/ui/field.test.tsx
  • src/react/components/ui/input-otp.test.tsx
  • src/react/components/ui/meter.test.tsx
  • src/react/components/ui/navigation-menu.test.tsx
  • src/react/components/ui/pagination.test.tsx
  • src/react/components/ui/scroll-area.test.tsx
  • src/testing/dom-globals.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 326 1949 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

The repository's anti-slop ratchet rejected four `as unknown as` casts I carried
over from the per-file harnesses. Baselining them would have made a shared
module the place new copies come from, which is the opposite of the point.

- matchMedia now returns an interface describing what the stub actually
  provides, instead of asserting an object literal into the full DOM type.
- Globals are read and written through Reflect, so there is no assertion about
  the shape of globalThis.
- Frame ids are plain numbers here, so the Set is typed accordingly and both
  casts disappear.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 21, 2026
The shared JSDOM helper must install the same constructors each test previously exposed, and framework imports must remain in the repository-defined group.

Constraint: Preserve every migrated test global exactly.

Rejected: Remove the local harness wrappers | Their local options document per-component browser requirements.

Confidence: high

Scope-risk: narrow

Directive: Keep per-test windowGlobals aligned with the globals replaced by this harness.

Tested: Deno 2.7.7 fmt and check on all migrated files; 16 suites and 97 test steps passed.

Not-tested: Full repository suite.
@kojiwakayama
kojiwakayama removed this pull request from the merge queue due to a manual request Aug 21, 2026
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 5de7ece Aug 21, 2026
34 checks passed
@kojiwakayama
kojiwakayama deleted the test/shared-jsdom-harness branch August 21, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant