Files
OmniRoute/tests/unit/ui/orchestrationPage.test.tsx
Diego Rodrigues de Sa e Souza d2a027a156 feat(dashboard): orchestration canvas fase 2 — quick wins + hardening (2.3/2.4/2.8/2.11/2.12, #12270, #12271) (#12393)
* feat(dashboard): orchestration pure snapshot filter (search + chips)

* feat(dashboard): orchestration collapse-by-source in flow projection

* refactor(dashboard): orchestration model polish (#12271 model items)

* feat(dashboard): theme-aware orchestration status tokens (2.12)

* feat(dashboard): particle StatusEdge for orchestration canvas (2.11)

* feat(dashboard): orchestration search/filter chips + collapse in URL (2.3+2.4)

* feat(dashboard): drawer trace copy + error/a11y hardening (2.8, #12270)

* chore(dashboard): orchestration UI hardening (#12270)

* fix(dashboard): type-safe source read in AgentsTab collapse handler

* fix(dashboard): pass staleSince into the sourceStale message

* test(dashboard): orchestration missing coverage + fase2 i18n (#12271)

* refactor(dashboard): split orchestration page/toolbar/drawer helpers under complexity ratchet

---------

Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com>
2026-09-01 23:13:47 -03:00

257 lines
8.5 KiB
TypeScript

// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, afterEach, vi } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (k: string, v?: Record<string, unknown>) =>
v ? `${k}:${JSON.stringify(v)}` : k,
}));
const replaceMock = vi.fn();
const searchState = { current: "tab=overview" };
vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams(searchState.current),
useRouter: () => ({ replace: replaceMock }),
usePathname: () => "/dashboard/orchestration",
}));
const DEFAULT_SNAPSHOT = {
nodes: [{ id: "orchestrator", kind: "orchestrator", label: "OmniRoute" }],
edges: [],
sources: [],
generatedAt: "x",
};
let snapshot: typeof DEFAULT_SNAPSHOT = DEFAULT_SNAPSHOT;
const setShowCompletedMock = vi.fn();
const refetchMock = vi.fn();
vi.mock("@/app/(dashboard)/dashboard/orchestration/hooks/useOrchestrationSnapshot", () => ({
useOrchestrationSnapshot: () => ({
snapshot,
isLoading: false,
showCompleted: false,
setShowCompleted: setShowCompletedMock,
refetch: refetchMock,
}),
}));
vi.mock("@/hooks/useLiveDashboard", () => ({
useLiveComboStatus: () => ({
comboEvents: [],
activeCombos: new Set<string>(),
isConnected: true,
}),
}));
vi.mock("@/hooks/useProviderBreakerHealth", () => ({
useProviderBreakerHealth: () => ({ providerHealth: {}, connectionHealth: {} }),
}));
const agentsTabCalls: Record<string, unknown>[] = [];
vi.mock("@/app/(dashboard)/dashboard/orchestration/tabs/AgentsTab", () => ({
AgentsTab: (props: Record<string, unknown>) => {
agentsTabCalls.push(props);
return <div data-testid="agents-tab-stub" />;
},
}));
vi.mock("@/app/(dashboard)/dashboard/orchestration/tabs/RoutingTab", () => ({
RoutingTab: () => <div data-testid="routing-tab-stub" />,
}));
const overviewTabCalls: Record<string, unknown>[] = [];
vi.mock("@/app/(dashboard)/dashboard/orchestration/tabs/OverviewTab", () => ({
OverviewTab: (props: Record<string, unknown>) => {
overviewTabCalls.push(props);
return <div data-testid="overview-tab-stub" />;
},
}));
const drawerCalls: Record<string, unknown>[] = [];
vi.mock("@/app/(dashboard)/dashboard/orchestration/drawer/OrchestrationDrawer", () => ({
OrchestrationDrawer: (props: Record<string, unknown>) => {
drawerCalls.push(props);
return <div data-testid="drawer-stub" />;
},
}));
import OrchestrationPageClient from "@/app/(dashboard)/dashboard/orchestration/OrchestrationPageClient";
function render(el: React.ReactElement) {
const c = document.createElement("div");
document.body.appendChild(c);
const root = createRoot(c);
act(() => root.render(el));
return {
c,
cleanup: () => {
act(() => root.unmount());
c.remove();
},
};
}
afterEach(() => {
document.body.innerHTML = "";
replaceMock.mockClear();
searchState.current = "tab=overview";
snapshot = DEFAULT_SNAPSHOT;
agentsTabCalls.length = 0;
overviewTabCalls.length = 0;
drawerCalls.length = 0;
});
describe("OrchestrationPageClient", () => {
it("renders the tab selected by the URL (?tab=overview)", () => {
const { c, cleanup } = render(<OrchestrationPageClient />);
expect(c.querySelector('[data-testid="overview-tab-stub"]')).toBeTruthy();
expect(c.querySelector('[data-testid="agents-tab-stub"]')).toBeFalsy();
expect(c.querySelector('[data-testid="routing-tab-stub"]')).toBeFalsy();
cleanup();
});
it("clicking the Agents tab button pushes ?tab=agents via router.replace", () => {
const { c, cleanup } = render(<OrchestrationPageClient />);
const agentsTabButton = Array.from(c.querySelectorAll('[role="tab"]')).find(
(el) => el.textContent === "tabAgents"
) as HTMLButtonElement;
expect(agentsTabButton).toBeTruthy();
act(() => {
agentsTabButton.click();
});
expect(replaceMock).toHaveBeenCalledTimes(1);
const [url, opts] = replaceMock.mock.calls[0];
expect(url).toContain("tab=agents");
expect(opts).toEqual({ scroll: false });
cleanup();
});
it("?q=login filters the snapshot passed to OverviewTab down to matching work nodes", () => {
snapshot = {
nodes: [
{ id: "orchestrator", kind: "orchestrator", label: "OmniRoute" },
{
id: "cloud-agent:1",
kind: "work",
source: "cloud-agent",
state: "running",
label: "login flow fix",
},
{
id: "a2a:2",
kind: "work",
source: "a2a",
state: "failed",
label: "unrelated task",
},
],
edges: [],
sources: [],
generatedAt: "x",
} as never;
searchState.current = "tab=overview&q=login";
const { cleanup } = render(<OrchestrationPageClient />);
const lastProps = overviewTabCalls.at(-1) as { snapshot: typeof DEFAULT_SNAPSHOT };
const ids = lastProps.snapshot.nodes.map((n) => n.id);
expect(ids).toContain("cloud-agent:1");
expect(ids).not.toContain("a2a:2");
cleanup();
});
it("clicking a state chip in the toolbar sets ?state= via router.replace", () => {
searchState.current = "tab=agents";
const { c, cleanup } = render(<OrchestrationPageClient />);
const runningChip = Array.from(c.querySelectorAll("button")).find(
(el) => el.textContent === "stateRunning"
) as HTMLButtonElement;
expect(runningChip).toBeTruthy();
act(() => {
runningChip.click();
});
expect(replaceMock).toHaveBeenCalledTimes(1);
const [url] = replaceMock.mock.calls[0];
expect(url).toContain("state=running");
cleanup();
});
it("toggling a collapse from AgentsTab writes ?collapsed= via router.replace", () => {
searchState.current = "tab=agents";
const { cleanup } = render(<OrchestrationPageClient />);
const props = agentsTabCalls.at(-1) as { onToggleCollapse: (s: string) => void };
act(() => {
props.onToggleCollapse("a2a");
});
expect(replaceMock).toHaveBeenCalledTimes(1);
const [url] = replaceMock.mock.calls[0];
expect(url).toContain("collapsed=a2a");
cleanup();
});
it("shows a clear-filters button only when the filter is non-empty, and it resets q/state/source/provider", () => {
searchState.current = "tab=agents";
const r1 = render(<OrchestrationPageClient />);
expect(
Array.from(r1.c.querySelectorAll("button")).find((el) => el.textContent === "clearFilters")
).toBeFalsy();
r1.cleanup();
searchState.current =
"tab=agents&q=login&state=running&source=a2a&provider=devin&collapsed=a2a";
const r2 = render(<OrchestrationPageClient />);
const clearButton = Array.from(r2.c.querySelectorAll("button")).find(
(el) => el.textContent === "clearFilters"
) as HTMLButtonElement;
expect(clearButton).toBeTruthy();
act(() => {
clearButton.click();
});
expect(replaceMock).toHaveBeenCalledTimes(1);
const [url] = replaceMock.mock.calls[0];
expect(url).not.toContain("q=");
expect(url).not.toContain("state=");
expect(url).not.toContain("source=");
expect(url).not.toContain("provider=");
expect(url).toContain("collapsed=a2a");
r2.cleanup();
});
it("?node=<id> opens the drawer with the matching node; removing the param closes it", () => {
snapshot = {
nodes: [
{ id: "orchestrator", kind: "orchestrator", label: "OmniRoute" },
{
id: "cloud-agent:1",
kind: "work",
source: "cloud-agent",
state: "running",
label: "task A",
},
],
edges: [],
sources: [],
generatedAt: "x",
} as never;
searchState.current = "tab=agents&node=cloud-agent:1";
const r1 = render(<OrchestrationPageClient />);
expect((drawerCalls.at(-1) as { node: { id: string } | null }).node?.id).toBe("cloud-agent:1");
r1.cleanup();
searchState.current = "tab=agents";
const r2 = render(<OrchestrationPageClient />);
expect((drawerCalls.at(-1) as { node: { id: string } | null }).node).toBeNull();
r2.cleanup();
});
it("clicking an overflow node (via AgentsTab's onNodeClick) navigates to ?tab=overview and clears ?node", () => {
searchState.current = "tab=agents&node=cloud-agent:1";
const { cleanup } = render(<OrchestrationPageClient />);
const props = agentsTabCalls.at(-1) as { onNodeClick: (id: string) => void };
act(() => {
props.onNodeClick("overflow:cloud-agent");
});
expect(replaceMock).toHaveBeenCalledTimes(1);
const [url] = replaceMock.mock.calls[0];
expect(url).toContain("tab=overview");
expect(url).not.toContain("node=");
cleanup();
});
});