Files
copilotkit--copilotkit/showcase/shell-dashboard/src/components/__tests__/overlay-selector-integration.test.tsx
T
2026-07-13 12:58:18 +08:00

614 lines
22 KiB
TypeScript

/**
* Integration tests for overlay selectors — renders with REAL child
* components (CellStatus, DocsRow, DepthChip, etc.) and verifies that
* toggling overlay selectors actually controls what the user sees.
*
* Only the data layer is mocked (live-status, docs-status, useLastTransition,
* PocketBase client) to provide predictable test data.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render } from "@testing-library/react";
import { ComposedCell } from "../composed-cell";
import type { Overlay } from "../composed-cell";
import type { CellContext } from "@/components/feature-grid";
import type { CatalogCell } from "@/components/depth-utils";
import type { LiveStatusMap, StatusRow } from "@/lib/live-status";
// ---------------------------------------------------------------------------
// Data-layer mocks — real UI components, fake data
// ---------------------------------------------------------------------------
// Mock PocketBase client (imported by useLastTransition)
vi.mock("@/lib/pb", () => ({
pb: {
filter: vi.fn(() => ""),
collection: vi.fn(() => ({
getList: vi.fn(() => Promise.resolve({ items: [] })),
})),
},
}));
// Mock docs-status to return predictable doc state
vi.mock("@/lib/docs-status", () => ({
getDocsStatus: vi.fn(() => ({ og: "ok", shell: "ok" })),
}));
// Mock useLastTransition (lazy tooltip fetch) — no PB calls in tests
vi.mock("@/hooks/useLastTransition", () => ({
useLastTransition: vi.fn(() => ({ row: null, loaded: false, error: null })),
deriveFromTo: vi.fn(() => ({ from: null, to: null })),
}));
// Mock the docs-status.json data import used by docs-status.ts
vi.mock("@/data/docs-status.json", () => ({
default: {
features: {
"chat-text": { og: "ok", shell: "ok" },
"agentic-chat": { og: "ok", shell: "ok" },
},
},
}));
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// A recent timestamp so green rows are not treated as stale by the
// staleness downgrade in resolveCell (compares against Date.now()).
const FRESH_OBSERVED_AT = new Date().toISOString();
function makeStatusRow(
dimension: string,
slug: string,
featureId?: string,
state: "green" | "red" | "degraded" = "green",
): StatusRow {
const key = featureId
? `${dimension}:${slug}/${featureId}`
: `${dimension}:${slug}`;
return {
id: `row-${key}`,
key,
dimension,
state,
signal: null,
observed_at: FRESH_OBSERVED_AT,
transitioned_at: FRESH_OBSERVED_AT,
fail_count: 0,
first_failure_at: null,
};
}
function buildLiveStatusMap(slug: string, featureId: string): LiveStatusMap {
const map: LiveStatusMap = new Map();
const rows = [
makeStatusRow("health", slug),
makeStatusRow("e2e", slug, featureId),
makeStatusRow("smoke", slug),
makeStatusRow("d5", slug, featureId),
makeStatusRow("d6", slug, featureId),
makeStatusRow("agent", slug),
makeStatusRow("chat", slug),
];
for (const r of rows) {
map.set(r.key, r);
}
return map;
}
function makeCtx(overrides?: Partial<CellContext>): CellContext {
const slug = "next";
const featureId = "chat-text";
return {
integration: {
name: "Next.js",
slug,
category: "framework",
language: "TypeScript",
description: "Next.js integration",
repo: "https://github.com/test/next",
backend_url: "https://next.test",
deployed: true,
features: [featureId],
demos: [
{
id: featureId,
name: "Chat Text",
description: "Basic text chat",
tags: [],
route: "/chat",
},
],
},
feature: {
id: featureId,
name: "Chat Text",
category: "chat-ui",
description: "Basic text chat feature",
kind: "primary",
},
demo: {
id: featureId,
name: "Chat Text",
description: "Basic text chat",
tags: [],
route: "/chat",
},
hostedUrl: "https://next.test/chat",
shellUrl: "http://localhost:3000",
liveStatus: buildLiveStatusMap(slug, featureId),
connection: "live",
...overrides,
};
}
function makeTestingCtx(): CellContext {
return makeCtx({
feature: {
id: "agentic-chat",
name: "Agentic Chat",
category: "chat-ui",
description: "Agentic chat feature",
kind: "testing",
},
demo: {
id: "agentic-chat",
name: "Agentic Chat",
description: "Agentic chat testing",
tags: [],
route: "/agentic-chat",
},
liveStatus: buildLiveStatusMap("next", "agentic-chat"),
});
}
function makeCatalogCell(overrides?: Partial<CatalogCell>): CatalogCell {
return {
id: "next/chat-text",
integration: "next",
integration_name: "Next.js",
feature: "chat-text",
feature_name: "Chat Text",
status: "wired",
max_depth: 3,
category: "chat-ui",
category_name: "Chat & UI",
...overrides,
};
}
function overlaySet(...overlays: Overlay[]): Set<Overlay> {
return new Set(overlays);
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("Overlay selector integration — real UI components", () => {
beforeEach(() => {
vi.clearAllMocks();
});
// -------------------------------------------------------------------------
// 1. Links only
// -------------------------------------------------------------------------
it("links only: Demo/Code links visible, no depth/badges/docs", () => {
const ctx = makeCtx();
const { getByText, queryByTestId, queryByText } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("links")} />,
);
// Links layer content present
expect(getByText("Demo")).toBeInTheDocument();
expect(getByText("</>")).toBeInTheDocument();
// No depth chip
expect(queryByTestId("depth-chip")).not.toBeInTheDocument();
expect(queryByTestId("depth-layer")).not.toBeInTheDocument();
// No health badges (API, UI, 1P)
expect(queryByText("API")).not.toBeInTheDocument();
expect(queryByText("UI")).not.toBeInTheDocument();
expect(queryByText("1P")).not.toBeInTheDocument();
// No docs indicators
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 2. Depth only
// -------------------------------------------------------------------------
it("depth only: depth chip visible, no links/badges/docs", () => {
const ctx = makeCtx();
const catalogCell = makeCatalogCell();
const { getByTestId, queryByText } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet("depth")}
catalogCell={catalogCell}
/>,
);
// Depth chip present — real DepthChip renders data-testid="depth-chip"
expect(getByTestId("depth-layer")).toBeInTheDocument();
expect(getByTestId("depth-chip")).toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
expect(queryByText("</>")).not.toBeInTheDocument();
// No health badges
expect(queryByText("UI")).not.toBeInTheDocument();
// No docs indicators
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 3. Health only — the critical case (no docs indicators must appear)
// -------------------------------------------------------------------------
it("health only: API/UI/1P badges visible, NO docs indicators", () => {
// `agentic-chat` is a real CATALOG_TO_D5_KEY entry, so its d5 row resolves
// green and the 1P badge renders. An UNMAPPED feature's 1P badge is gray
// "?" and hidden by design (resolveD5Row returns null for unmapped
// features, matching resolveD5 / isD5Green) — "don't show tests that
// don't exist".
const ctx = makeCtx({
feature: {
id: "agentic-chat",
name: "Agentic Chat",
category: "chat-ui",
description: "Agentic chat feature",
kind: "primary",
},
liveStatus: buildLiveStatusMap("next", "agentic-chat"),
});
const { getByTestId, getByText, queryByText, queryByTestId } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("health")} />,
);
// Health layer present with real badges
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("API")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
expect(getByText("1P")).toBeInTheDocument();
// No docs indicators — this is the critical regression test for B2's fix
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
expect(queryByTestId("docs-layer")).not.toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
// No depth chip
expect(queryByTestId("depth-layer")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 4. Docs only
// -------------------------------------------------------------------------
it("docs only: docs-og/docs-shell indicators visible, no links/badges/depth", () => {
const ctx = makeCtx();
const { getByTestId, getByText, queryByText, queryByTestId } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("docs")} />,
);
// Docs layer present with real DocsRow content
expect(getByTestId("docs-layer")).toBeInTheDocument();
// DocsRow renders "docs-og" and "docs-shell" labels
expect(getByText("docs-og")).toBeInTheDocument();
expect(getByText("docs-shell")).toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
// No health badges
expect(queryByText("UI")).not.toBeInTheDocument();
// No depth chip
expect(queryByTestId("depth-layer")).not.toBeInTheDocument();
expect(queryByTestId("depth-chip")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 5. Parity only — empty cell
// -------------------------------------------------------------------------
it("parity only: renders empty cell (no per-cell content)", () => {
const ctx = makeCtx();
const { getByTestId, queryByTestId, queryByText } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("parity")} />,
);
// Parity produces no per-cell content → empty cell
expect(getByTestId("composed-cell-empty")).toBeInTheDocument();
expect(queryByTestId("composed-cell")).not.toBeInTheDocument();
// Nothing visible
expect(queryByText("Demo")).not.toBeInTheDocument();
expect(queryByText("UI")).not.toBeInTheDocument();
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByTestId("depth-chip")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 6. Health + Docs — both badges AND docs indicators visible
// -------------------------------------------------------------------------
it("health + docs: badges AND docs indicators both visible", () => {
// `agentic-chat` is a real CATALOG_TO_D5_KEY entry so the 1P badge has a
// resolved (green) d5 row to render; an unmapped feature's 1P badge is
// hidden by design. The docs-status mock also covers `agentic-chat`, so
// the docs-og / docs-shell indicators still resolve.
const ctx = makeCtx({
feature: {
id: "agentic-chat",
name: "Agentic Chat",
category: "chat-ui",
description: "Agentic chat feature",
kind: "primary",
},
liveStatus: buildLiveStatusMap("next", "agentic-chat"),
});
const { getByTestId, getByText, queryByText, queryByTestId } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("health", "docs")} />,
);
// Health layer
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("API")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
expect(getByText("1P")).toBeInTheDocument();
// Docs layer
expect(getByTestId("docs-layer")).toBeInTheDocument();
expect(getByText("docs-og")).toBeInTheDocument();
expect(getByText("docs-shell")).toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
// No depth chip
expect(queryByTestId("depth-layer")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 7. All overlays — everything visible, correct stacking order
// -------------------------------------------------------------------------
it("all overlays: every layer visible in correct stacking order", () => {
const ctx = makeCtx();
const catalogCell = makeCatalogCell();
const { getByTestId, getByText } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet(
"links",
"depth",
"health",
"docs",
"parity",
"d6",
)}
catalogCell={catalogCell}
/>,
);
// All content layers present
expect(getByText("Demo")).toBeInTheDocument();
expect(getByTestId("depth-layer")).toBeInTheDocument();
expect(getByTestId("depth-chip")).toBeInTheDocument();
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
expect(getByTestId("docs-layer")).toBeInTheDocument();
expect(getByText("docs-og")).toBeInTheDocument();
expect(getByText("docs-shell")).toBeInTheDocument();
// Verify stacking order: links, depth, health, docs
const composedCell = getByTestId("composed-cell");
const children = Array.from(composedCell.children);
expect(children.length).toBe(4); // parity and d6 add no content layer
// First child: links (contains "Demo")
expect(children[0]?.textContent).toContain("Demo");
// Second child: depth (contains depth-chip)
expect(
children[1]?.querySelector("[data-testid='depth-chip']"),
).toBeTruthy();
// Third child: health (contains the UI badge — renamed from E2E)
expect(children[2]?.textContent).toContain("UI");
// Fourth child: docs (contains docs-og)
expect(children[3]?.textContent).toContain("docs-og");
});
// -------------------------------------------------------------------------
// 8. Health without Docs — the critical regression test for B2's fix
// -------------------------------------------------------------------------
it("health without docs: badges visible, docs-og/docs-shell NOT visible", () => {
const ctx = makeCtx();
const { getByTestId, getByText, queryByText, queryByTestId } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("health")} />,
);
// Health badges present
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
// Docs explicitly absent — this is the bug B2 fixed: CellStatus used to
// render DocsRow, so "health only" would still show docs indicators.
// After B2's fix, DocsRow is only in DocsLayer (gated on overlays.has("docs")).
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
expect(queryByTestId("docs-layer")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 9. Testing-kind features: 1P badges hidden
// -------------------------------------------------------------------------
it("testing-kind feature with health: 1P badges hidden, API/BE still shown", () => {
const ctx = makeTestingCtx();
const { getByTestId, getByText, queryByText } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("health")} />,
);
// Health layer present
expect(getByTestId("health-layer")).toBeInTheDocument();
// API and BE badges still visible for testing-kind
expect(getByText("API")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
// 1P hidden for testing-kind features (CellStatus hides them)
expect(queryByText("1P")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 10. Testing-kind feature with docs overlay: DocsLayer still renders
// -------------------------------------------------------------------------
it("testing-kind feature with docs overlay: docs indicators visible", () => {
const ctx = makeTestingCtx();
const { getByTestId, getByText } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("docs")} />,
);
// DocsLayer renders for testing features when docs overlay is active
expect(getByTestId("docs-layer")).toBeInTheDocument();
expect(getByText("docs-og")).toBeInTheDocument();
expect(getByText("docs-shell")).toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 11. Catalog preset (links + health + docs) — all three layers visible
// -------------------------------------------------------------------------
it("Catalog preset (links+health+docs): all three layers visible", () => {
const ctx = makeCtx();
// Catalog preset from overlay-types.ts: ["links", "health", "docs"]
const { getByText, getByTestId, queryByTestId } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet("links", "health", "docs")}
/>,
);
// Links
expect(getByText("Demo")).toBeInTheDocument();
expect(getByText("</>")).toBeInTheDocument();
// Health badges
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
// Docs indicators
expect(getByTestId("docs-layer")).toBeInTheDocument();
expect(getByText("docs-og")).toBeInTheDocument();
expect(getByText("docs-shell")).toBeInTheDocument();
// No depth chip (not in Catalog preset)
expect(queryByTestId("depth-layer")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 12. Assessment preset (depth + health) — depth chips + badges, NO docs
// -------------------------------------------------------------------------
it("Assessment preset (depth+health): depth chip + badges, NO docs", () => {
const ctx = makeCtx();
const catalogCell = makeCatalogCell();
// Assessment preset from overlay-types.ts: ["depth", "health"]
const { getByTestId, getByText, queryByText, queryByTestId } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet("depth", "health")}
catalogCell={catalogCell}
/>,
);
// Depth chip
expect(getByTestId("depth-layer")).toBeInTheDocument();
expect(getByTestId("depth-chip")).toBeInTheDocument();
// Health badges
expect(getByTestId("health-layer")).toBeInTheDocument();
expect(getByText("UI")).toBeInTheDocument();
// No docs — critical: Assessment does NOT include docs
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
expect(queryByTestId("docs-layer")).not.toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 13. Parity Review preset (depth + parity) — depth chips only
// -------------------------------------------------------------------------
it("Parity Review preset (depth+parity): depth chip only, no badges/docs/links", () => {
const ctx = makeCtx();
const catalogCell = makeCatalogCell();
// Parity Review preset from overlay-types.ts: ["depth", "parity"]
const { getByTestId, queryByText, queryByTestId } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet("depth", "parity")}
catalogCell={catalogCell}
/>,
);
// Depth chip present
expect(getByTestId("depth-layer")).toBeInTheDocument();
expect(getByTestId("depth-chip")).toBeInTheDocument();
// No health badges (health overlay not active)
expect(queryByTestId("health-layer")).not.toBeInTheDocument();
// No docs
expect(queryByText("docs-og")).not.toBeInTheDocument();
expect(queryByText("docs-shell")).not.toBeInTheDocument();
expect(queryByTestId("docs-layer")).not.toBeInTheDocument();
// No links
expect(queryByText("Demo")).not.toBeInTheDocument();
});
// -------------------------------------------------------------------------
// 14. Health badges show correct green tone from real resolveCell
// -------------------------------------------------------------------------
it("health layer renders real badge tones from live status data", () => {
const ctx = makeCtx();
const { getByText } = render(
<ComposedCell ctx={ctx} overlays={overlaySet("health")} />,
);
// With green live status rows, the UI badge (renamed from E2E) should show the green check
const e2eBadge = getByText("UI");
expect(e2eBadge).toBeInTheDocument();
// The badge label "✓" (green state) should appear as a sibling span
const e2eContainer = e2eBadge.closest("[class*='whitespace-nowrap']");
expect(e2eContainer?.textContent).toContain("✓");
});
// -------------------------------------------------------------------------
// 15. Depth chip shows correct depth from real deriveDepth
// -------------------------------------------------------------------------
it("depth layer renders real depth chip with correct D-level", () => {
const ctx = makeCtx();
const catalogCell = makeCatalogCell();
const { getByTestId } = render(
<ComposedCell
ctx={ctx}
overlays={overlaySet("depth")}
catalogCell={catalogCell}
/>,
);
const chip = getByTestId("depth-chip");
expect(chip).toBeInTheDocument();
// The depth chip renders "D<n>" text — with full green live status and
// health+agent+e2e+chat all green, deriveDepth should yield D4
const depthAttr = chip.getAttribute("data-depth");
expect(depthAttr).toBeTruthy();
expect(chip.textContent).toMatch(/^D[0-6]$/);
});
});