All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
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 => (
|
|
<div data-testid="widget-grid" data-editing={isEditing}>
|
|
{layout.map((item) => (
|
|
<div key={item.i} data-testid={`widget-${item.i}`}>
|
|
{item.i}
|
|
</div>
|
|
))}
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
// 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<void> => {
|
|
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
|
|
|
|
render(<DashboardPage />);
|
|
|
|
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<void> => {
|
|
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(<DashboardPage />);
|
|
|
|
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(<DashboardPage />);
|
|
|
|
expect(screen.getByText("Loading dashboard...")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should fall back to default layout on API error", async (): Promise<void> => {
|
|
vi.mocked(layoutsApi.fetchDefaultLayout).mockRejectedValue(new Error("Network error"));
|
|
|
|
render(<DashboardPage />);
|
|
|
|
await waitFor((): void => {
|
|
expect(screen.getByTestId("widget-grid")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should render Dashboard heading", async (): Promise<void> => {
|
|
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
|
|
|
|
render(<DashboardPage />);
|
|
|
|
await waitFor((): void => {
|
|
expect(screen.getByText("Dashboard")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should render Edit Layout button", async (): Promise<void> => {
|
|
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
|
|
|
|
render(<DashboardPage />);
|
|
|
|
await waitFor((): void => {
|
|
expect(screen.getByText("Edit Layout")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should toggle edit mode on button click", async (): Promise<void> => {
|
|
vi.mocked(layoutsApi.fetchDefaultLayout).mockResolvedValue(mockExistingLayout);
|
|
|
|
render(<DashboardPage />);
|
|
|
|
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");
|
|
});
|
|
});
|