fix(CQ-WEB-10): Add loading/error states to pages with mock data
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Convert tasks, calendar, and dashboard pages from synchronous mock data
to async loading pattern with useState/useEffect. Each page now shows a
loading state via child components while data loads, and displays a
PDA-friendly amber-styled message with a retry button if loading fails.

This prepares these pages for real API integration by establishing the
async data flow pattern. Child components (TaskList, Calendar, dashboard
widgets) already handled isLoading props — now the pages actually use them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-06 18:40:21 -06:00
parent 952eeb7323
commit bfeea743f7
6 changed files with 284 additions and 42 deletions

View File

@@ -0,0 +1,85 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import DashboardPage from "./page";
// Mock dashboard widgets
vi.mock("@/components/dashboard/RecentTasksWidget", () => ({
RecentTasksWidget: ({
tasks,
isLoading,
}: {
tasks: unknown[];
isLoading: boolean;
}): React.JSX.Element => (
<div data-testid="recent-tasks">
{isLoading ? "Loading tasks" : `${String(tasks.length)} tasks`}
</div>
),
}));
vi.mock("@/components/dashboard/UpcomingEventsWidget", () => ({
UpcomingEventsWidget: ({
events,
isLoading,
}: {
events: unknown[];
isLoading: boolean;
}): React.JSX.Element => (
<div data-testid="upcoming-events">
{isLoading ? "Loading events" : `${String(events.length)} events`}
</div>
),
}));
vi.mock("@/components/dashboard/QuickCaptureWidget", () => ({
QuickCaptureWidget: (): React.JSX.Element => <div data-testid="quick-capture">Quick Capture</div>,
}));
vi.mock("@/components/dashboard/DomainOverviewWidget", () => ({
DomainOverviewWidget: ({
tasks,
isLoading,
}: {
tasks: unknown[];
isLoading: boolean;
}): React.JSX.Element => (
<div data-testid="domain-overview">
{isLoading ? "Loading overview" : `${String(tasks.length)} tasks overview`}
</div>
),
}));
describe("DashboardPage", (): void => {
it("should render the page title", (): void => {
render(<DashboardPage />);
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Dashboard");
});
it("should show loading state initially", (): void => {
render(<DashboardPage />);
expect(screen.getByTestId("recent-tasks")).toHaveTextContent("Loading tasks");
expect(screen.getByTestId("upcoming-events")).toHaveTextContent("Loading events");
expect(screen.getByTestId("domain-overview")).toHaveTextContent("Loading overview");
});
it("should render all widgets with data after loading", async (): Promise<void> => {
render(<DashboardPage />);
await waitFor((): void => {
expect(screen.getByTestId("recent-tasks")).toHaveTextContent("4 tasks");
expect(screen.getByTestId("upcoming-events")).toHaveTextContent("3 events");
expect(screen.getByTestId("domain-overview")).toHaveTextContent("4 tasks overview");
expect(screen.getByTestId("quick-capture")).toBeInTheDocument();
});
});
it("should have proper layout structure", (): void => {
const { container } = render(<DashboardPage />);
const main = container.querySelector("main");
expect(main).toBeInTheDocument();
});
it("should render the welcome subtitle", (): void => {
render(<DashboardPage />);
expect(screen.getByText(/Welcome back/)).toBeInTheDocument();
});
});