mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test(ui): register global afterEach(cleanup) — no React tree outlives its test
The desk-caught defect: the full ui run reported "Errors 1" with a fully green test count (ReferenceError: window is not defined at react-dom performWorkOnRootViaSchedulerTask, thrown after environment teardown), which flips the exit code that gates the A/B pin — and per vitest's own warning can produce FALSE POSITIVE tests. STRUCTURAL CAUSE (verified at source, not inferred): vitest.config.ts leaves the "globals" option at its default FALSE, and React Testing Library only registers its automatic afterEach(cleanup) when the framework afterEach is a global. So auto-cleanup NEVER RAN: 117 of 153 test files left their React trees mounted past the test. A mounted tree still has scheduled work; when that task lands after the environment is disposed it dereferences a window that no longer exists. The components are innocent — TerminalPreviewPopover already cancels its rAF in effect cleanup, but effect cleanup only runs on UNMOUNT, which never happened. Fix: register cleanup globally in test/setup.ts (unmount, never suppress) — the R5-family "work must not outlive its context" pattern in the React harness. RED pin: harness-teardown-hygiene.test.tsx fails deterministically without the registration (a rendered tree survives into the next test). HONESTY NOTE: the original error did NOT reproduce in 6 full-suite runs at this SHA before the fix (it is load/order-sensitive), so this is not an A/B proof that the flake is gone — it removes the documented precondition of the reported stack and pins that removal deterministically. 197/197 files, 1723 tests green after the change (no test depended on leaked DOM); tsc 0. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
29d28fb684
commit
feeda3a808
2 files changed
+64
No files matched your search
@@ -0,0 +1,46 @@
|
||||
// UI HARNESS TEARDOWN HYGIENE — the R5-family "work must not outlive its
|
||||
// context" pattern, in the React harness.
|
||||
//
|
||||
// THE DEFECT (desk-caught, main c230909cf): the full ui run reported
|
||||
// "Errors 1" with a fully GREEN test count — ReferenceError: window is not
|
||||
// defined at react-dom performWorkOnRootViaSchedulerTask, thrown AFTER the
|
||||
// test environment was torn down. Vitest's own warning: an unhandled
|
||||
// post-teardown error can cause FALSE POSITIVE tests, and it flips the exit
|
||||
// code that gates the A/B pin.
|
||||
//
|
||||
// THE STRUCTURAL CAUSE (verified at source): vitest.config.ts does not set
|
||||
// `globals`, so it defaults FALSE — and React Testing Library only registers
|
||||
// its automatic `afterEach(cleanup)` when the framework's afterEach is
|
||||
// available as a global. With globals:false that auto-cleanup NEVER RUNS, so
|
||||
// every test file that does not call cleanup itself (117 of 153 at the time
|
||||
// of writing) leaves its React tree MOUNTED past the test. A mounted tree can
|
||||
// still have scheduled work (React schedules through its own task queue); if
|
||||
// that task lands after the environment is disposed, it dereferences a
|
||||
// `window` that no longer exists. The components are innocent — e.g.
|
||||
// TerminalPreviewPopover already cancels its rAF in the effect cleanup — but
|
||||
// that cleanup only runs ON UNMOUNT, which never happens.
|
||||
//
|
||||
// THE FIX: register cleanup globally in test/setup.ts, so every test unmounts
|
||||
// its tree (cancelling pending work) rather than suppressing the error.
|
||||
//
|
||||
// This file pins the PRECONDITION deterministically: with the fix, a test
|
||||
// that renders leaves nothing behind for the next test. Without it, the leak
|
||||
// is visible — which is the same leak that lets work outlive the environment.
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
|
||||
describe("ui harness: every test unmounts its tree (global auto-cleanup registered)", () => {
|
||||
it("renders a probe tree and does NOT unmount it explicitly", () => {
|
||||
render(<div data-testid="teardown-hygiene-probe">probe</div>);
|
||||
expect(document.querySelectorAll("[data-testid='teardown-hygiene-probe']").length).toBe(1);
|
||||
});
|
||||
|
||||
it("the NEXT test starts with a clean DOM — the previous tree was unmounted by the harness", () => {
|
||||
// RED without a global afterEach(cleanup): the probe from the previous
|
||||
// test is still mounted here, and (crucially) so is every other suite's
|
||||
// tree in a full run — the population whose scheduled work outlives the
|
||||
// environment.
|
||||
expect(document.querySelectorAll("[data-testid='teardown-hygiene-probe']").length).toBe(0);
|
||||
expect(document.body.innerHTML).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,21 @@
|
||||
// HARNESS TEARDOWN HYGIENE (desk-caught post-teardown ReferenceError, 2026-08-07):
|
||||
// vitest.config.ts leaves `globals` at its default FALSE, so React Testing
|
||||
// Library's automatic afterEach(cleanup) never registers — every test file that
|
||||
// does not clean up itself leaves its React tree MOUNTED. A mounted tree can
|
||||
// still have scheduled work; when that task lands after the environment is
|
||||
// disposed it dereferences a `window` that no longer exists ("ReferenceError:
|
||||
// window is not defined" at performWorkOnRootViaSchedulerTask), which vitest
|
||||
// warns can produce FALSE POSITIVE tests and which flips the run's exit code
|
||||
// while every test reports green. Registering cleanup here unmounts each tree —
|
||||
// cancelling the components' own effect cleanups (rAF, listeners, observers) —
|
||||
// so no work outlives its context. The cure is unmounting, never suppression.
|
||||
import { afterEach } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// Polyfill ResizeObserver for jsdom (required by React Flow)
|
||||
globalThis.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
|
||||
Reference in new issue
Block a user