d7faa617df
The bundle at /login was executing but the React tree never painted —
no console errors, no fallback UI, just an empty <div id='root'>.
Add three layers of defense so a future failure of this shape is
captured instead of being silently swallowed:
1. window 'error' and 'unhandledrejection' listeners in main.tsx,
printing structured context to console.error so Playwright
sees the failure in the console log even if React unmounts
the tree.
2. A top-level <ErrorBoundary> in main.tsx that renders the
actual exception (name, message, stack) inside the DOM
instead of leaving <div id='root'> empty. The boundary also
logs to console.error via componentDidCatch.
3. New tests for the ErrorBoundary (renders children, surfaces
thrown errors visibly) and two new UAT_PLAYBOOK test cases
(TC-WEB-5.1.6 / 5.1.7) that explicitly assert the
'never-blank-root' invariant on UAT.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { render, screen, cleanup } from "@testing-library/react";
|
|
import { ErrorBoundary } from "../ErrorBoundary";
|
|
|
|
function ThrowingChild(): never {
|
|
throw new Error("synthetic render-time failure for GRO-2094");
|
|
}
|
|
|
|
function GoodChild() {
|
|
return <div data-testid="good-child">ok</div>;
|
|
}
|
|
|
|
describe("ErrorBoundary (GRO-2094)", () => {
|
|
let errorSpy: ReturnType<typeof vi.spyOn>;
|
|
|
|
beforeEach(() => {
|
|
// React 18+ logs caught render errors to console.error via React's own
|
|
// instrumentation; suppress it so test output is clean but capture it
|
|
// for an assertion below.
|
|
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
errorSpy.mockRestore();
|
|
cleanup();
|
|
});
|
|
|
|
it("renders children when nothing throws", () => {
|
|
render(
|
|
<ErrorBoundary>
|
|
<GoodChild />
|
|
</ErrorBoundary>
|
|
);
|
|
expect(screen.getByTestId("good-child")).toBeInTheDocument();
|
|
expect(screen.queryByTestId("error-boundary")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the error visibly when a child throws during render", () => {
|
|
render(
|
|
<ErrorBoundary>
|
|
<ThrowingChild />
|
|
</ErrorBoundary>
|
|
);
|
|
|
|
const fallback = screen.getByTestId("error-boundary");
|
|
expect(fallback).toBeInTheDocument();
|
|
const message = screen.getByTestId("error-boundary-message");
|
|
// The actual exception is shown — no more silent blank root.
|
|
expect(message.textContent).toContain("synthetic render-time failure for GRO-2094");
|
|
// The boundary also calls console.error so it shows up in the Playwright
|
|
// console log even if the DOM-rendered fallback is somehow missed.
|
|
expect(errorSpy).toHaveBeenCalled();
|
|
});
|
|
});
|