|
|
import { describe, it, expect, vi, beforeEach } from "vitest"; |
|
|
import { render, screen, fireEvent } from "@testing-library/react"; |
|
|
import { ErrorBoundary } from "../ErrorBoundary"; |
|
|
|
|
|
|
|
|
function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) { |
|
|
if (shouldThrow) { |
|
|
throw new Error("Test error message"); |
|
|
} |
|
|
return <div>Normal content</div>; |
|
|
} |
|
|
|
|
|
describe("ErrorBoundary", () => { |
|
|
|
|
|
beforeEach(() => { |
|
|
vi.spyOn(console, "error").mockImplementation(() => {}); |
|
|
}); |
|
|
|
|
|
it("renders children when there is no error", () => { |
|
|
render( |
|
|
<ErrorBoundary> |
|
|
<div data-testid="child">Child content</div> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
expect(screen.getByTestId("child")).toBeInTheDocument(); |
|
|
expect(screen.getByText("Child content")).toBeInTheDocument(); |
|
|
}); |
|
|
|
|
|
it("displays error UI when child throws", () => { |
|
|
render( |
|
|
<ErrorBoundary> |
|
|
<ThrowingComponent shouldThrow={true} /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
expect(screen.getByText("Something went wrong")).toBeInTheDocument(); |
|
|
expect( |
|
|
screen.getByText(/an unexpected error occurred/i), |
|
|
).toBeInTheDocument(); |
|
|
expect(screen.getByRole("button", { name: /try again/i })).toBeInTheDocument(); |
|
|
}); |
|
|
|
|
|
it("shows error details in expandable section", () => { |
|
|
render( |
|
|
<ErrorBoundary> |
|
|
<ThrowingComponent shouldThrow={true} /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
|
|
|
const details = screen.getByText("Error details"); |
|
|
fireEvent.click(details); |
|
|
|
|
|
expect(screen.getByText("Test error message")).toBeInTheDocument(); |
|
|
}); |
|
|
|
|
|
it("resets error state when Try Again is clicked", () => { |
|
|
|
|
|
let shouldThrow = true; |
|
|
const StatefulThrower = () => { |
|
|
if (shouldThrow) { |
|
|
throw new Error("Test error"); |
|
|
} |
|
|
return <div>Normal content</div>; |
|
|
}; |
|
|
|
|
|
const { rerender } = render( |
|
|
<ErrorBoundary> |
|
|
<StatefulThrower /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
|
|
|
expect(screen.getByText("Something went wrong")).toBeInTheDocument(); |
|
|
|
|
|
|
|
|
shouldThrow = false; |
|
|
|
|
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /try again/i })); |
|
|
|
|
|
|
|
|
rerender( |
|
|
<ErrorBoundary> |
|
|
<StatefulThrower /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
|
|
|
expect(screen.getByText("Normal content")).toBeInTheDocument(); |
|
|
expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument(); |
|
|
}); |
|
|
|
|
|
it("renders custom fallback when provided", () => { |
|
|
const customFallback = <div data-testid="custom-fallback">Custom error UI</div>; |
|
|
|
|
|
render( |
|
|
<ErrorBoundary fallback={customFallback}> |
|
|
<ThrowingComponent shouldThrow={true} /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
expect(screen.getByTestId("custom-fallback")).toBeInTheDocument(); |
|
|
expect(screen.getByText("Custom error UI")).toBeInTheDocument(); |
|
|
|
|
|
expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument(); |
|
|
}); |
|
|
|
|
|
it("logs error to console when error is caught", () => { |
|
|
const consoleSpy = vi.spyOn(console, "error"); |
|
|
|
|
|
render( |
|
|
<ErrorBoundary> |
|
|
<ThrowingComponent shouldThrow={true} /> |
|
|
</ErrorBoundary>, |
|
|
); |
|
|
|
|
|
|
|
|
expect(consoleSpy).toHaveBeenCalled(); |
|
|
}); |
|
|
}); |
|
|
|