fix(web): update calendar and knowledge tests for real API integration
All checks were successful
ci/woodpecker/push/web Pipeline was successful

Calendar page and KnowledgeGraphViewer tests were broken after PRs #474
and #476 wired these components to real API data. Tests now mock
fetchEvents, fetchKnowledgeGraph, useWorkspaceId, MosaicSpinner, and
elkjs following the same pattern established in tasks/page.test.tsx.

Refs #469

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 23:03:19 -06:00
parent 3d5b50af11
commit d0a1b9281c
2 changed files with 133 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import type { Event } from "@mosaic/shared";
import CalendarPage from "./page";
// Mock the Calendar component
@@ -15,15 +16,94 @@ vi.mock("@/components/calendar/Calendar", () => ({
),
}));
// 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 fetchEvents
const mockFetchEvents = vi.fn<() => Promise<Event[]>>();
vi.mock("@/lib/api/events", () => ({
fetchEvents: (...args: unknown[]): Promise<Event[]> => mockFetchEvents(...(args as [])),
}));
const fakeEvents: Event[] = [
{
id: "event-1",
title: "Team standup",
description: "Daily standup meeting",
startTime: new Date("2026-02-20T09:00:00Z"),
endTime: new Date("2026-02-20T09:30:00Z"),
allDay: false,
location: null,
recurrence: null,
creatorId: "user-1",
projectId: null,
workspaceId: "ws-1",
metadata: {},
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "event-2",
title: "Sprint planning",
description: "Bi-weekly sprint planning",
startTime: new Date("2026-02-21T14:00:00Z"),
endTime: new Date("2026-02-21T15:00:00Z"),
allDay: false,
location: null,
recurrence: null,
creatorId: "user-1",
projectId: null,
workspaceId: "ws-1",
metadata: {},
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "event-3",
title: "All-day workshop",
description: null,
startTime: new Date("2026-02-22T00:00:00Z"),
endTime: null,
allDay: true,
location: "Conference Room A",
recurrence: null,
creatorId: "user-1",
projectId: null,
workspaceId: "ws-1",
metadata: {},
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
];
describe("CalendarPage", (): void => {
beforeEach((): void => {
vi.clearAllMocks();
mockUseWorkspaceId.mockReturnValue("ws-1");
mockFetchEvents.mockResolvedValue(fakeEvents);
});
it("should render the page title", (): void => {
render(<CalendarPage />);
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Calendar");
});
it("should show loading state initially", (): void => {
// Never resolve so we stay in loading state
// eslint-disable-next-line @typescript-eslint/no-empty-function
mockFetchEvents.mockReturnValue(new Promise<Event[]>(() => {}));
render(<CalendarPage />);
expect(screen.getByTestId("calendar")).toHaveTextContent("Loading");
expect(screen.getByTestId("mosaic-spinner")).toBeInTheDocument();
});
it("should render the Calendar with events after loading", async (): Promise<void> => {
@@ -43,4 +123,31 @@ describe("CalendarPage", (): void => {
render(<CalendarPage />);
expect(screen.getByText("View your schedule at a glance")).toBeInTheDocument();
});
it("should show empty state when no events exist", async (): Promise<void> => {
mockFetchEvents.mockResolvedValue([]);
render(<CalendarPage />);
await waitFor((): void => {
expect(screen.getByText("No events scheduled")).toBeInTheDocument();
});
});
it("should show error state on API failure", async (): Promise<void> => {
mockFetchEvents.mockRejectedValue(new Error("Network error"));
render(<CalendarPage />);
await waitFor((): void => {
expect(screen.getByText("Network error")).toBeInTheDocument();
});
expect(screen.getByRole("button", { name: /try again/i })).toBeInTheDocument();
});
it("should not fetch when workspace ID is not available", async (): Promise<void> => {
mockUseWorkspaceId.mockReturnValue(null);
render(<CalendarPage />);
// Wait a tick to ensure useEffect ran
await waitFor((): void => {
expect(mockFetchEvents).not.toHaveBeenCalled();
});
});
});

View File

@@ -7,6 +7,30 @@ import * as knowledgeApi from "@/lib/api/knowledge";
// Mock the knowledge API
vi.mock("@/lib/api/knowledge");
// Mock MosaicSpinner to expose a test ID
vi.mock("@/components/ui/MosaicSpinner", () => ({
MosaicSpinner: ({ label }: { label?: string }): React.JSX.Element => (
<div data-testid="loading-spinner">{label ?? "Loading..."}</div>
),
}));
// Mock elkjs since it requires APIs not available in test environment
vi.mock("elkjs/lib/elk.bundled.js", () => ({
default: class ELK {
layout(graph: {
children?: { id: string }[];
}): Promise<{ children: { id: string; x: number; y: number }[] }> {
return Promise.resolve({
children: (graph.children ?? []).map((child: { id: string }, i: number) => ({
id: child.id,
x: i * 100,
y: i * 100,
})),
});
}
},
}));
// Mock Next.js router
const mockPush = vi.fn();
vi.mock("next/navigation", () => ({