import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { Task } from "@mosaic/shared"; import { TaskStatus, TaskPriority } from "@mosaic/shared"; import TasksPage from "./page"; // Mock the TaskList component vi.mock("@/components/tasks/TaskList", () => ({ TaskList: ({ tasks, isLoading }: { tasks: unknown[]; isLoading: boolean }): React.JSX.Element => (
{isLoading ? "Loading" : `${String(tasks.length)} tasks`}
), })); // Mock MosaicSpinner vi.mock("@/components/ui/MosaicSpinner", () => ({ MosaicSpinner: ({ label }: { label?: string }): React.JSX.Element => (
{label ?? "Loading..."}
), })); // Mock useWorkspaceId const mockUseWorkspaceId = vi.fn<() => string | null>(); vi.mock("@/lib/hooks", () => ({ useWorkspaceId: (): string | null => mockUseWorkspaceId(), })); // Mock fetchTasks const mockFetchTasks = vi.fn<() => Promise>(); vi.mock("@/lib/api/tasks", () => ({ fetchTasks: (...args: unknown[]): Promise => mockFetchTasks(...(args as [])), })); const fakeTasks: Task[] = [ { id: "task-1", title: "Test task 1", description: "Description 1", status: TaskStatus.IN_PROGRESS, priority: TaskPriority.HIGH, dueDate: new Date("2026-02-01"), creatorId: "user-1", assigneeId: "user-1", workspaceId: "ws-1", projectId: null, parentId: null, sortOrder: 0, metadata: {}, completedAt: null, createdAt: new Date("2026-01-28"), updatedAt: new Date("2026-01-28"), }, { id: "task-2", title: "Test task 2", description: "Description 2", status: TaskStatus.NOT_STARTED, priority: TaskPriority.MEDIUM, dueDate: new Date("2026-02-02"), creatorId: "user-1", assigneeId: "user-1", workspaceId: "ws-1", projectId: null, parentId: null, sortOrder: 1, metadata: {}, completedAt: null, createdAt: new Date("2026-01-28"), updatedAt: new Date("2026-01-28"), }, ]; describe("TasksPage", (): void => { beforeEach((): void => { vi.clearAllMocks(); mockUseWorkspaceId.mockReturnValue("ws-1"); mockFetchTasks.mockResolvedValue(fakeTasks); }); it("should render the page title", (): void => { render(); expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Tasks"); }); it("should show loading spinner initially", (): void => { // Never resolve so we stay in loading state // eslint-disable-next-line @typescript-eslint/no-empty-function mockFetchTasks.mockReturnValue(new Promise(() => {})); render(); expect(screen.getByTestId("mosaic-spinner")).toBeInTheDocument(); }); it("should render the TaskList with tasks after loading", async (): Promise => { render(); await waitFor((): void => { expect(screen.getByTestId("task-list")).toHaveTextContent("2 tasks"); }); }); it("should show empty state when no tasks exist", async (): Promise => { mockFetchTasks.mockResolvedValue([]); render(); await waitFor((): void => { expect(screen.getByText("No tasks found")).toBeInTheDocument(); }); }); it("should show error state on API failure", async (): Promise => { mockFetchTasks.mockRejectedValue(new Error("Network error")); render(); await waitFor((): void => { expect(screen.getByText("Network error")).toBeInTheDocument(); }); expect(screen.getByRole("button", { name: /try again/i })).toBeInTheDocument(); }); it("should retry fetching on retry button click", async (): Promise => { mockFetchTasks.mockRejectedValueOnce(new Error("Network error")); render(); await waitFor((): void => { expect(screen.getByText("Network error")).toBeInTheDocument(); }); mockFetchTasks.mockResolvedValueOnce(fakeTasks); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: /try again/i })); await waitFor((): void => { expect(screen.getByTestId("task-list")).toHaveTextContent("2 tasks"); }); }); it("should have proper layout structure", (): void => { const { container } = render(); const main = container.querySelector("main"); expect(main).toBeInTheDocument(); }); it("should render the subtitle text", (): void => { render(); expect(screen.getByText("Organize your work at your own pace")).toBeInTheDocument(); }); it("should not fetch when workspace ID is not available", async (): Promise => { mockUseWorkspaceId.mockReturnValue(null); render(); // Wait a tick to ensure useEffect ran await waitFor((): void => { expect(mockFetchTasks).not.toHaveBeenCalled(); }); }); });