import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest";
import { render, screen, waitFor, act } from "@testing-library/react";
import DashboardPage from "./page";
import * as layoutsApi from "@/lib/api/layouts";
import type { UserLayout, WidgetPlacement } from "@mosaic/shared";
// ResizeObserver is not available in jsdom
beforeAll((): void => {
global.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));
});
// Mock WidgetGrid to avoid react-grid-layout dependency in tests
vi.mock("@/components/widgets/WidgetGrid", () => ({
WidgetGrid: ({
layout,
isEditing,
}: {
layout: WidgetPlacement[];
isEditing?: boolean;
}): React.JSX.Element => (
{layout.map((item) => (
{item.i}
))}
),
}));
// Mock hooks
vi.mock("@/lib/hooks", () => ({
useWorkspaceId: (): string | null => "ws-test-123",
}));
// Mock layout API
vi.mock("@/lib/api/layouts");
const mockExistingLayout: UserLayout = {
id: "layout-1",
workspaceId: "ws-test-123",
userId: "user-1",
name: "Default",
isDefault: true,
layout: [
{ i: "TasksWidget-default", x: 0, y: 0, w: 4, h: 2 },
{ i: "CalendarWidget-default", x: 4, y: 0, w: 4, h: 2 },
],
metadata: {},
createdAt: new Date("2026-01-01T00:00:00Z"),
updatedAt: new Date("2026-01-01T00:00:00Z"),
};
describe("DashboardPage", (): void => {
beforeEach((): void => {
vi.clearAllMocks();
});
it("should render WidgetGrid with saved layout", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
render();
await waitFor((): void => {
expect(screen.getByTestId("widget-grid")).toBeInTheDocument();
});
expect(screen.getByTestId("widget-TasksWidget-default")).toBeInTheDocument();
expect(screen.getByTestId("widget-CalendarWidget-default")).toBeInTheDocument();
});
it("should create default layout when none exists", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(null);
vi.mocked(layoutsApi.createLayout).mockResolvedValue({
...mockExistingLayout,
layout: [{ i: "TasksWidget-default", x: 0, y: 0, w: 4, h: 2 }],
});
render();
await waitFor((): void => {
expect(layoutsApi.createLayout).toHaveBeenCalledWith("ws-test-123", {
name: "Default",
isDefault: true,
layout: expect.arrayContaining([
expect.objectContaining({ i: "TasksWidget-default" }),
]) as WidgetPlacement[],
});
});
});
it("should show loading spinner initially", (): void => {
// Never-resolving promise to test loading state
vi.mocked(layoutsApi.fetchDefaultLayout).mockReturnValue(
// eslint-disable-next-line @typescript-eslint/no-empty-function -- intentionally never-resolving
new Promise(() => {})
);
render();
expect(screen.getByText("Loading dashboard...")).toBeInTheDocument();
});
it("should fall back to default layout on API error", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockRejectedValue(new Error("Network error"));
render();
await waitFor((): void => {
expect(screen.getByTestId("widget-grid")).toBeInTheDocument();
});
});
it("should render Dashboard heading", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
render();
await waitFor((): void => {
expect(screen.getByText("Dashboard")).toBeInTheDocument();
});
});
it("should render Edit Layout button", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
render();
await waitFor((): void => {
expect(screen.getByText("Edit Layout")).toBeInTheDocument();
});
});
it("should toggle edit mode on button click", async (): Promise => {
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
render();
await waitFor((): void => {
expect(screen.getByText("Edit Layout")).toBeInTheDocument();
});
act((): void => {
screen.getByText("Edit Layout").click();
});
expect(screen.getByText("Done")).toBeInTheDocument();
expect(screen.getByTestId("widget-grid").getAttribute("data-editing")).toBe("true");
});
});