diff --git a/src/client/components/ErrorBoundary.test.tsx b/src/client/components/ErrorBoundary.test.tsx new file mode 100644 index 0000000..e7f881e --- /dev/null +++ b/src/client/components/ErrorBoundary.test.tsx @@ -0,0 +1,50 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach } from "vitest"; +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom/vitest"; +import { ErrorBoundary } from "./ErrorBoundary"; + +function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) { + if (shouldThrow) { + throw new Error("Test render error"); + } + return
Child content
; +} + +describe("ErrorBoundary", () => { + beforeEach(() => { + // Suppress React error boundary console errors in test output + vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + it("renders children when no error occurs", () => { + render( + + + + ); + expect(screen.getByText("Child content")).toBeInTheDocument(); + }); + + it("renders error UI when child throws", () => { + render( + + + + ); + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + expect(screen.getByText("Test render error")).toBeInTheDocument(); + }); + + it("shows try again button in error state", () => { + render( + + + + ); + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + expect(screen.getByText("Try again")).toBeInTheDocument(); + expect(screen.getByText("An error occurred while rendering this view.")).toBeInTheDocument(); + }); +}); diff --git a/src/client/components/ErrorBoundary.tsx b/src/client/components/ErrorBoundary.tsx new file mode 100644 index 0000000..78f5768 --- /dev/null +++ b/src/client/components/ErrorBoundary.tsx @@ -0,0 +1,57 @@ +import React from "react"; + +interface Props { + children: React.ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends React.Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + render() { + if (!this.state.hasError) { + return this.props.children; + } + + return ( +
+
+
+ + + +
+

Something went wrong

+

+ An error occurred while rendering this view. +

+ {this.state.error && ( +
+              {this.state.error.message}
+            
+ )} + +
+
+ ); + } +}