// @vitest-environment jsdom
import { vi, test, describe, beforeEach, afterEach, expect } from "vitest";
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import "vitest-dom/extend-expect";
import { ErrorBoundary } from "../dashboard";
vi.mock("react-router", async () => {
const actual = await vi.importActual("react-router");
return {
...actual,
useRouteError: vi.fn(),
useSearchParams: vi.fn(),
isRouteErrorResponse: vi.fn(),
};
});
const { useRouteError, useSearchParams, isRouteErrorResponse } = await import(
"react-router"
);
describe("Dashboard ErrorBoundary", () => {
const originalLocation = global.window?.location;
const mockReload = vi.fn();
beforeEach(() => {
Object.defineProperty(global.window, "location", {
value: {
reload: mockReload,
href: "",
},
writable: true,
});
vi.mocked(useSearchParams).mockReturnValue([
new URLSearchParams("site=example.com&interval=7d"),
] as any);
});
afterEach(() => {
cleanup();
mockReload.mockClear();
vi.clearAllMocks();
if (originalLocation) {
global.window.location = originalLocation;
}
});
describe("RouteErrorResponse handling", () => {
test("displays configuration error for missing CF_ACCOUNT_ID (501)", () => {
const error = {
status: 501,
data: "Missing credentials: CF_ACCOUNT_ID is not set.",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(screen.getByText("Configuration Error")).toBeInTheDocument();
expect(
screen.getByText(
"Missing Cloudflare Account ID configuration.",
),
).toBeInTheDocument();
expect(
screen.getByText(
"Please ensure CF_ACCOUNT_ID is properly configured in your environment variables.",
),
).toBeInTheDocument();
expect(screen.queryByText("Try Again")).not.toBeInTheDocument();
// Configuration errors are not actionable, so no buttons should be shown
expect(
screen.queryByText("Back to Dashboard"),
).not.toBeInTheDocument();
});
test("displays configuration error for missing CF_BEARER_TOKEN (501)", () => {
const error = {
status: 501,
data: "Missing credentials: CF_BEARER_TOKEN is not set.",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(screen.getByText("Configuration Error")).toBeInTheDocument();
expect(
screen.getByText(
"Missing Cloudflare Bearer Token configuration.",
),
).toBeInTheDocument();
expect(
screen.getByText(
"Please ensure CF_BEARER_TOKEN is properly configured in your environment variables.",
),
).toBeInTheDocument();
expect(screen.queryByText("Try Again")).not.toBeInTheDocument();
});
test("displays server error for 500 status", () => {
const error = {
status: 500,
data: "Internal server error",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(screen.getByText("Server Error")).toBeInTheDocument();
expect(
screen.getByText("The server encountered an internal error."),
).toBeInTheDocument();
expect(
screen.getByText(
"This is likely a temporary issue. Please try again in a few moments.",
),
).toBeInTheDocument();
expect(screen.getByText("Try Again")).toBeInTheDocument();
});
test("displays generic error for other HTTP status codes", () => {
const error = {
status: 400,
data: "Bad request",
statusText: "Bad Request",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(screen.getByText("Error 400")).toBeInTheDocument();
expect(screen.getByText("Bad request")).toBeInTheDocument();
expect(
screen.getByText(
"Please try refreshing the page or contact support if the issue persists.",
),
).toBeInTheDocument();
expect(screen.getByText("Try Again")).toBeInTheDocument();
});
});
describe("Error object handling", () => {
test("displays Analytics Engine error", () => {
const error = new Error(
"Failed to fetch data from Analytics Engine",
);
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(
screen.getByText("Analytics Engine Error"),
).toBeInTheDocument();
expect(
screen.getByText(
"Failed to connect to Cloudflare Analytics Engine.",
),
).toBeInTheDocument();
expect(
screen.getByText(
"This could be due to network issues or Analytics Engine being temporarily unavailable. Please try again in a few moments.",
),
).toBeInTheDocument();
expect(screen.getByText("Try Again")).toBeInTheDocument();
});
test("displays authentication error", () => {
const error = new Error("Authentication failed: invalid token");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(
screen.getByText("Authentication Error"),
).toBeInTheDocument();
expect(
screen.getByText("Authentication failed: invalid token"),
).toBeInTheDocument();
expect(
screen.getByText(
"Please check your credentials and try logging in again.",
),
).toBeInTheDocument();
expect(screen.queryByText("Try Again")).not.toBeInTheDocument();
});
test("displays invalid interval error", () => {
const error = new Error("Invalid interval provided");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(screen.getByText("Invalid Time Range")).toBeInTheDocument();
expect(
screen.getByText(
"The selected time interval is not supported.",
),
).toBeInTheDocument();
expect(
screen.getByText(
"Please select a different time range from the dropdown.",
),
).toBeInTheDocument();
});
test("displays generic application error", () => {
const error = new Error("Something went wrong");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(screen.getByText("Application Error")).toBeInTheDocument();
expect(
screen.getByText("Something went wrong"),
).toBeInTheDocument();
expect(
screen.getByText(
"Please try refreshing the page or contact support if the issue persists.",
),
).toBeInTheDocument();
expect(screen.getByText("Try Again")).toBeInTheDocument();
});
});
describe("Context display", () => {
test("shows context information when available", () => {
const error = new Error("Test error");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
vi.mocked(useSearchParams).mockReturnValue([
new URLSearchParams("site=example.com&interval=30d"),
] as any);
render();
expect(
screen.getByText("Context when error occurred:"),
).toBeInTheDocument();
expect(screen.getByText("example.com")).toBeInTheDocument();
expect(screen.getByText("30d")).toBeInTheDocument();
});
test("hides context when showContext is false", () => {
const error = {
status: 501,
data: "Missing credentials: CF_ACCOUNT_ID is not set.",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(
screen.queryByText("Context when error occurred:"),
).not.toBeInTheDocument();
});
});
describe("Button interactions", () => {
test("Try Again button reloads the page", () => {
const error = new Error("Test error");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
const tryAgainButton = screen.getByText("Try Again");
fireEvent.click(tryAgainButton);
expect(mockReload).toHaveBeenCalledTimes(1);
});
test("Back to Dashboard button navigates to dashboard", () => {
const error = new Error("Test error");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
const backButton = screen.getByText("Back to Dashboard");
fireEvent.click(backButton);
expect(global.window.location.href).toBe("/dashboard");
});
});
describe("Edge cases", () => {
test("handles null error gracefully", () => {
vi.mocked(useRouteError).mockReturnValue(null);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(screen.getByText("Dashboard Error")).toBeInTheDocument();
expect(
screen.getByText(
"An unexpected error occurred while loading the dashboard.",
),
).toBeInTheDocument();
});
test("handles Response error without data", () => {
const error = {
status: 500,
data: null,
statusText: "Internal Server Error",
};
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(true);
render();
expect(screen.getByText("Server Error")).toBeInTheDocument();
});
test("displays warning emoji and proper UI structure", () => {
const error = new Error("Test error");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
const { container } = render();
expect(screen.getByText("⚠️")).toBeInTheDocument();
expect(container.querySelector(".max-w-2xl")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Try Again" }),
).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Back to Dashboard" }),
).toBeInTheDocument();
});
test("logs error to console", () => {
const consoleSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});
const error = new Error("Test error for logging");
vi.mocked(useRouteError).mockReturnValue(error);
vi.mocked(isRouteErrorResponse).mockReturnValue(false);
render();
expect(consoleSpy).toHaveBeenCalledWith("Dashboard Error:", error);
consoleSpy.mockRestore();
});
});
});