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"); }); });