test(web): update tasks page tests for real API integration (#475)
Some checks failed
ci/woodpecker/push/web Pipeline failed
Some checks failed
ci/woodpecker/push/web Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #475.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
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
|
||||
@@ -9,21 +12,121 @@ vi.mock("@/components/tasks/TaskList", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock MosaicSpinner
|
||||
vi.mock("@/components/ui/MosaicSpinner", () => ({
|
||||
MosaicSpinner: ({ label }: { label?: string }): React.JSX.Element => (
|
||||
<div data-testid="mosaic-spinner">{label ?? "Loading..."}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock useWorkspaceId
|
||||
const mockUseWorkspaceId = vi.fn<() => string | null>();
|
||||
vi.mock("@/lib/hooks", () => ({
|
||||
useWorkspaceId: (): string | null => mockUseWorkspaceId(),
|
||||
}));
|
||||
|
||||
// Mock fetchTasks
|
||||
const mockFetchTasks = vi.fn<() => Promise<Task[]>>();
|
||||
vi.mock("@/lib/api/tasks", () => ({
|
||||
fetchTasks: (...args: unknown[]): Promise<Task[]> => 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(<TasksPage />);
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Tasks");
|
||||
});
|
||||
|
||||
it("should show loading state initially", (): void => {
|
||||
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<Task[]>(() => {}));
|
||||
render(<TasksPage />);
|
||||
expect(screen.getByTestId("task-list")).toHaveTextContent("Loading");
|
||||
expect(screen.getByTestId("mosaic-spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the TaskList with tasks after loading", async (): Promise<void> => {
|
||||
render(<TasksPage />);
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("task-list")).toHaveTextContent("4 tasks");
|
||||
expect(screen.getByTestId("task-list")).toHaveTextContent("2 tasks");
|
||||
});
|
||||
});
|
||||
|
||||
it("should show empty state when no tasks exist", async (): Promise<void> => {
|
||||
mockFetchTasks.mockResolvedValue([]);
|
||||
render(<TasksPage />);
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByText("No tasks found")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show error state on API failure", async (): Promise<void> => {
|
||||
mockFetchTasks.mockRejectedValue(new Error("Network error"));
|
||||
render(<TasksPage />);
|
||||
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<void> => {
|
||||
mockFetchTasks.mockRejectedValueOnce(new Error("Network error"));
|
||||
render(<TasksPage />);
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,4 +140,14 @@ describe("TasksPage", (): void => {
|
||||
render(<TasksPage />);
|
||||
expect(screen.getByText("Organize your work at your own pace")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not fetch when workspace ID is not available", async (): Promise<void> => {
|
||||
mockUseWorkspaceId.mockReturnValue(null);
|
||||
render(<TasksPage />);
|
||||
|
||||
// Wait a tick to ensure useEffect ran
|
||||
await waitFor((): void => {
|
||||
expect(mockFetchTasks).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user