Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #460.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import DashboardPage from "./page";
|
||||
import { fetchDashboardSummary } from "@/lib/api/dashboard";
|
||||
|
||||
// Mock Phase 3 dashboard widgets
|
||||
vi.mock("@/components/dashboard/DashboardMetrics", () => ({
|
||||
@@ -27,29 +28,85 @@ vi.mock("@/components/dashboard/TokenBudget", () => ({
|
||||
TokenBudget: (): React.JSX.Element => <div data-testid="token-budget">Token Budget</div>,
|
||||
}));
|
||||
|
||||
// Mock hooks and API calls
|
||||
vi.mock("@/lib/hooks", () => ({
|
||||
useWorkspaceId: (): string | null => "ws-test-123",
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/api/dashboard", () => ({
|
||||
fetchDashboardSummary: vi.fn().mockResolvedValue({
|
||||
metrics: {
|
||||
activeAgents: 5,
|
||||
tasksCompleted: 42,
|
||||
totalTasks: 100,
|
||||
tasksInProgress: 10,
|
||||
activeProjects: 3,
|
||||
errorRate: 0.5,
|
||||
},
|
||||
recentActivity: [],
|
||||
activeJobs: [],
|
||||
tokenBudget: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("DashboardPage", (): void => {
|
||||
it("should render the DashboardMetrics widget", (): void => {
|
||||
render(<DashboardPage />);
|
||||
expect(screen.getByTestId("dashboard-metrics")).toBeInTheDocument();
|
||||
beforeEach((): void => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(fetchDashboardSummary).mockResolvedValue({
|
||||
metrics: {
|
||||
activeAgents: 5,
|
||||
tasksCompleted: 42,
|
||||
totalTasks: 100,
|
||||
tasksInProgress: 10,
|
||||
activeProjects: 3,
|
||||
errorRate: 0.5,
|
||||
},
|
||||
recentActivity: [],
|
||||
activeJobs: [],
|
||||
tokenBudget: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the OrchestratorSessions widget", (): void => {
|
||||
it("should render the DashboardMetrics widget", async (): Promise<void> => {
|
||||
render(<DashboardPage />);
|
||||
expect(screen.getByTestId("orchestrator-sessions")).toBeInTheDocument();
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("dashboard-metrics")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the QuickActions widget", (): void => {
|
||||
it("should render the OrchestratorSessions widget", async (): Promise<void> => {
|
||||
render(<DashboardPage />);
|
||||
expect(screen.getByTestId("quick-actions")).toBeInTheDocument();
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("orchestrator-sessions")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the ActivityFeed widget", (): void => {
|
||||
it("should render the QuickActions widget", async (): Promise<void> => {
|
||||
render(<DashboardPage />);
|
||||
expect(screen.getByTestId("activity-feed")).toBeInTheDocument();
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("quick-actions")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the TokenBudget widget", (): void => {
|
||||
it("should render the ActivityFeed widget", async (): Promise<void> => {
|
||||
render(<DashboardPage />);
|
||||
expect(screen.getByTestId("token-budget")).toBeInTheDocument();
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("activity-feed")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the TokenBudget widget", async (): Promise<void> => {
|
||||
render(<DashboardPage />);
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByTestId("token-budget")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render error state when API fails", async (): Promise<void> => {
|
||||
vi.mocked(fetchDashboardSummary).mockRejectedValueOnce(new Error("Network error"));
|
||||
render(<DashboardPage />);
|
||||
await waitFor((): void => {
|
||||
expect(screen.getByText("Failed to load dashboard data")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import { DashboardMetrics } from "@/components/dashboard/DashboardMetrics";
|
||||
import { OrchestratorSessions } from "@/components/dashboard/OrchestratorSessions";
|
||||
import { QuickActions } from "@/components/dashboard/QuickActions";
|
||||
import { ActivityFeed } from "@/components/dashboard/ActivityFeed";
|
||||
import { TokenBudget } from "@/components/dashboard/TokenBudget";
|
||||
import { fetchDashboardSummary } from "@/lib/api/dashboard";
|
||||
import type { DashboardSummaryResponse } from "@/lib/api/dashboard";
|
||||
import { useWorkspaceId } from "@/lib/hooks";
|
||||
|
||||
export default function DashboardPage(): ReactElement {
|
||||
const workspaceId = useWorkspaceId();
|
||||
const [data, setData] = useState<DashboardSummaryResponse | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceId) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const wsId = workspaceId;
|
||||
let cancelled = false;
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
|
||||
async function loadSummary(): Promise<void> {
|
||||
try {
|
||||
const summary = await fetchDashboardSummary(wsId);
|
||||
if (!cancelled) {
|
||||
setData(summary);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.error("[Dashboard] Failed to fetch summary:", err);
|
||||
if (!cancelled) {
|
||||
setError("Failed to load dashboard data");
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadSummary();
|
||||
|
||||
return (): void => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [workspaceId]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<DashboardMetrics />
|
||||
<div className="dash-grid">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
|
||||
<OrchestratorSessions />
|
||||
<QuickActions />
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<ActivityFeed />
|
||||
<TokenBudget />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<DashboardMetrics />
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
padding: "12px 16px",
|
||||
marginBottom: 16,
|
||||
background: "rgba(229,72,77,0.1)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: "var(--r)",
|
||||
color: "var(--text)",
|
||||
fontSize: "0.85rem",
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<DashboardMetrics metrics={data?.metrics} />
|
||||
<div className="dash-grid">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
|
||||
<OrchestratorSessions />
|
||||
<OrchestratorSessions jobs={data?.activeJobs} />
|
||||
<QuickActions />
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<ActivityFeed />
|
||||
<TokenBudget />
|
||||
<ActivityFeed items={data?.recentActivity} />
|
||||
<TokenBudget budgets={data?.tokenBudget} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user