FN-7044: split Command Center area tests

Split the oversized Command Center area test suite into focused files while preserving shared fixtures and coverage.

- Extract shared date ranges, fixtures, and helpers into areas.test-harness.tsx.
- Move GitHub and Signals area assertions into a dedicated sibling suite.
- Keep the remaining areas.test.tsx below the 2000-line cap with existing coverage intact.

Files changed:
 .../areas/__tests__/areas.github-signals.test.tsx  | 406 +++++++++++++
 .../areas/__tests__/areas.test-harness.tsx         | 302 ++++++++++
 .../command-center/areas/__tests__/areas.test.tsx  | 648 +--------------------
 3 files changed, 730 insertions(+), 626 deletions(-)

Fusion-Task-Id: FN-7044

Fusion-Task-Lineage: 63886ec9-e9c3-410e-bbc6-c5116cddb168
This commit is contained in:
gsxdsm
2026-06-25 22:15:44 -07:00
parent 07209a4732
commit 9bd0853667
3 changed files with 730 additions and 626 deletions

View File

@@ -0,0 +1,406 @@
/*
FNXC:CommandCenter 2026-06-25-00:00:
FN-7044 extracts the GitHub and Signals Command Center area suites from areas.test.tsx so each focused test file remains under the 2000-line hard cap while preserving every assertion.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
// Mock the api() helper so the areas fetch deterministic fixtures. Keep these Vitest hoisted mocks inline in each split suite to avoid TDZ failures.
const mocks = vi.hoisted(() => ({
api: vi.fn(),
backfillGithubSourceIssueClosedAt: vi.fn(),
backfillCommitAssociationDiffStats: vi.fn(),
fetchOrgTree: vi.fn(),
fetchExecutorStats: vi.fn(),
fetchSettings: vi.fn(),
updateSettings: vi.fn(),
toggleEnginePause: vi.fn(),
appSettings: { globalPaused: false, enginePaused: false },
}));
const apiMock = mocks.api;
const backfillGithubSourceIssueClosedAtMock = mocks.backfillGithubSourceIssueClosedAt;
const backfillCommitAssociationDiffStatsMock = mocks.backfillCommitAssociationDiffStats;
const fetchOrgTreeMock = mocks.fetchOrgTree;
const fetchExecutorStatsMock = mocks.fetchExecutorStats;
const toggleEnginePauseMock = mocks.toggleEnginePause;
const appSettingsMock = mocks.appSettings;
vi.mock("../../../../api/legacy", () => ({
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
apiBackfillGithubSourceIssueClosedAt: (options?: { offset?: number; limit?: number }, projectId?: string) =>
mocks.backfillGithubSourceIssueClosedAt(options, projectId),
backfillCommitAssociationDiffStats: (options?: { dryRun?: boolean }, projectId?: string) =>
mocks.backfillCommitAssociationDiffStats(options, projectId),
fetchOrgTree: mocks.fetchOrgTree,
fetchExecutorStats: mocks.fetchExecutorStats,
fetchSettings: mocks.fetchSettings,
updateSettings: mocks.updateSettings,
}));
vi.mock("../../../../hooks/useAppSettings", () => ({
useAppSettings: () => ({
globalPaused: mocks.appSettings.globalPaused,
enginePaused: mocks.appSettings.enginePaused,
toggleEnginePause: mocks.toggleEnginePause,
}),
}));
import { GithubArea } from "../GithubArea";
import { SignalsArea } from "../SignalsArea";
import { customRange, githubFixture, range7d } from "./areas.test-harness";
beforeEach(() => {
apiMock.mockReset();
backfillGithubSourceIssueClosedAtMock.mockReset();
backfillCommitAssociationDiffStatsMock.mockReset();
fetchOrgTreeMock.mockReset();
fetchOrgTreeMock.mockResolvedValue([]);
fetchExecutorStatsMock.mockReset();
fetchExecutorStatsMock.mockResolvedValue({
globalPause: false,
enginePaused: false,
maxConcurrent: 2,
lastActivityAt: "2026-06-19T12:00:00.000Z",
});
mocks.fetchSettings.mockReset();
mocks.fetchSettings.mockResolvedValue({ heartbeatMultiplier: 1 });
mocks.updateSettings.mockReset();
mocks.updateSettings.mockResolvedValue({});
toggleEnginePauseMock.mockReset();
appSettingsMock.globalPaused = false;
appSettingsMock.enginePaused = false;
});
afterEach(() => {
vi.useRealTimers();
});
describe("GithubArea", () => {
it("renders filed/fixed/net stats, daily trend, and by-repo bars", async () => {
apiMock.mockResolvedValue(githubFixture());
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-filed").textContent).toContain("5");
expect(screen.getByTestId("cc-github-fixed").textContent).toContain("3");
expect(screen.getByTestId("cc-github-net").textContent).toContain("2");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.getByTestId("cc-github-line")).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed vs fixed share" })).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed vs fixed line" })).toBeTruthy();
expect(screen.getByTestId("cc-github-daily-trend")).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed" })).toBeTruthy();
expect(screen.getByRole("img", { name: "Fixed" })).toBeTruthy();
const repoChart = screen.getByRole("list", { name: "By repository" });
expect(within(repoChart).getByText("acme/alpha")).toBeTruthy();
expect(within(repoChart).getByLabelText("acme/alpha: 4 filed / 1 fixed")).toBeTruthy();
});
it("renders resolved issues with safe outbound links and approximation labels", async () => {
apiMock.mockResolvedValue({
...githubFixture(),
resolved: [
{
taskId: "FN-100",
taskTitle: "Fix alpha crash",
repo: "acme/alpha",
issueNumber: 123,
url: "https://github.com/acme/alpha/issues/123",
resolvedAt: "2026-06-10T12:34:56.000Z",
resolvedAtExact: true,
},
{
taskId: "FN-101",
taskTitle: "Patch unknown import",
repo: "(unknown)",
issueNumber: null,
url: null,
resolvedAt: "2026-06-09T08:00:00.000Z",
resolvedAtExact: false,
},
],
});
render(<GithubArea range={range7d} />);
const section = await screen.findByTestId("cc-github-resolved");
expect(section.textContent).toContain("Resolved issues");
expect(section.textContent).toContain("acme/alpha#123");
expect(section.textContent).toContain("Fix alpha crash");
expect(section.textContent).toContain("FN-100");
expect(section.textContent).toContain("(unknown)");
expect(section.textContent).toContain("Patch unknown import");
expect(section.textContent).toContain("approx");
expect(section.textContent).toContain("2026");
const linkedIssue = within(section).getByRole("link", { name: "Open GitHub issue acme/alpha#123" });
expect(linkedIssue.getAttribute("href")).toBe("https://github.com/acme/alpha/issues/123");
expect(linkedIssue.getAttribute("target")).toBe("_blank");
expect(linkedIssue.getAttribute("rel")).toBe("noopener noreferrer");
expect(within(section).queryByRole("link", { name: /unknown/i })).toBeNull();
});
it("omits the resolved issues section for an empty resolved list", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), resolved: [] });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.queryByTestId("cc-github-resolved")).toBeNull();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
});
it("renders the empty state without empty chart shells", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), filed: 0, fixed: 0, net: 0, daily: [], byRepo: [] });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-area-github").textContent).toContain("No GitHub issue activity");
expect(screen.getByTestId("cc-github-backfill-button")).toBeTruthy();
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
expect(screen.queryByTestId("cc-github-daily-trend")).toBeNull();
expect(screen.queryByTestId("cc-github-by-repo")).toBeNull();
});
it("renders loading and error states", async () => {
apiMock.mockImplementationOnce(() => new Promise(() => undefined));
const { unmount } = render(<GithubArea range={range7d} />);
expect(screen.getByTestId("cc-area-github-loading")).toBeTruthy();
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
unmount();
apiMock.mockRejectedValueOnce(new Error("github failed"));
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github-error");
expect(screen.getByTestId("cc-area-github-error").textContent).toContain("github failed");
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
});
it("handles undefined chart arrays and zero values without NaN output", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), filed: 1, fixed: 0, net: 1, daily: undefined, byRepo: undefined });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
expect(screen.queryByTestId("cc-github-daily-trend")).toBeNull();
expect(screen.queryByTestId("cc-github-by-repo")).toBeNull();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
});
it("keeps GitHub recharts safe for single-item and non-finite daily data", async () => {
apiMock.mockResolvedValue({
...githubFixture(),
filed: 1,
fixed: 1,
net: 0,
daily: [{ date: "2026-06-08", filed: Number.NaN, fixed: Number.POSITIVE_INFINITY }],
byRepo: [{ repo: "acme/broken", filed: Number.NaN, fixed: -1 }],
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.getByTestId("cc-github-line")).toBeTruthy();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("Infinity");
});
it("rejects an inverted custom range client-side without fetching", async () => {
render(<GithubArea range={customRange("2026-06-10", "2026-06-01")} />);
await waitFor(() => expect(apiMock).not.toHaveBeenCalled());
});
it("runs a single backfill batch and renders accumulated result counts", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 4,
filled: 2,
skipped: 1,
errors: 0,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
await screen.findByText(/Backfill complete/i);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledWith({ offset: 0, limit: 100 }, undefined);
const result = screen.getByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Scanned 4, filled 2, skipped 1, errors 0");
});
it("paginates multi-batch backfills with advancing offsets and summed counts", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock
.mockResolvedValueOnce({ scanned: 100, filled: 4, skipped: 90, errors: 1, hasMore: true })
.mockResolvedValueOnce({ scanned: 25, filled: 3, skipped: 22, errors: 0, hasMore: false });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
await waitFor(() => expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(2));
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenNthCalledWith(1, { offset: 0, limit: 100 }, undefined);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenNthCalledWith(2, { offset: 100, limit: 100 }, undefined);
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Scanned 125, filled 7, skipped 112, errors 1");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("shows the all-zero backfill as nothing to backfill instead of an error", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 0,
filled: 0,
skipped: 0,
errors: 0,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Nothing to backfill");
expect(result.className).not.toContain("cc-github-backfill-status--error");
});
it("surfaces nonzero backfill error counts without throwing", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 8,
filled: 1,
skipped: 5,
errors: 2,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("errors 2");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("captures endpoint failures in local error UI", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockRejectedValueOnce(new Error("endpoint failed"));
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("endpoint failed");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("disables and guards the button while a backfill is in flight", async () => {
apiMock.mockResolvedValue(githubFixture());
let resolveBackfill: ((value: { scanned: number; filled: number; skipped: number; errors: number; hasMore: boolean }) => void) | null = null;
backfillGithubSourceIssueClosedAtMock.mockImplementationOnce(
() => new Promise((resolve) => {
resolveBackfill = resolve;
}),
);
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
const button = screen.getByTestId("cc-github-backfill-button") as HTMLButtonElement;
fireEvent.click(button);
await waitFor(() => expect(button.disabled).toBe(true));
fireEvent.click(button);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(1);
resolveBackfill?.({ scanned: 1, filled: 1, skipped: 0, errors: 0, hasMore: false });
await screen.findByText(/Backfill complete/i);
});
it("stops a pathological always-has-more response at the max iteration guard", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValue({
scanned: 1,
filled: 0,
skipped: 1,
errors: 0,
hasMore: true,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByText(/safety limit/i);
expect(result.textContent).toContain("safety limit");
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(1000);
expect(screen.getByTestId("cc-github-backfill-result").textContent).toContain("Scanned 1000");
});
});
// FN-6684 Mission Control decision: no extra pie/line test here because MissionControlPanel already renders the live SDLC Funnel for its only quantitative distribution; adding a pie would duplicate that affordance.
describe("SignalsArea", () => {
it("renders the empty state (not an error) when the signals endpoint is missing", async () => {
apiMock.mockRejectedValue(new Error("API returned HTML instead of JSON (404)"));
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals-empty");
// Must not surface the error UI.
expect(screen.queryByTestId("cc-area-signals-error")).toBeNull();
expect(screen.queryByTestId("cc-signals-pie")).toBeNull();
});
it("renders signal metrics and status pie when data is present", async () => {
apiMock.mockResolvedValue({
totalSignals: 8,
open: 3,
resolved: 5,
mttr: { value: 42, unavailable: false },
bySource: [{ source: "sentry", count: 8 }],
bySeverity: [{ severity: "error", count: 8 }],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals");
expect(screen.getByTestId("cc-signals-total").textContent).toContain("8");
expect(screen.getByTestId("cc-signals-mttr").textContent).toContain("42");
expect(screen.getByTestId("cc-signals-pie")).toBeTruthy();
expect(screen.getByRole("img", { name: "Signal status share" })).toBeTruthy();
});
it("keeps signals pie safe for single-item and non-finite source/severity data", async () => {
apiMock.mockResolvedValue({
totalSignals: 1,
open: 1,
resolved: 0,
mttr: { value: null, unavailable: true },
bySource: [{ source: "broken", count: Number.NaN }],
bySeverity: [{ severity: "broken", count: Number.POSITIVE_INFINITY }],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals");
expect(screen.getByTestId("cc-signals-pie")).toBeTruthy();
expect(screen.getByTestId("cc-area-signals").textContent).not.toContain("NaN");
expect(screen.getByTestId("cc-area-signals").textContent).not.toContain("Infinity");
});
it("renders settled zero signals without a pie shell", async () => {
apiMock.mockResolvedValue({
totalSignals: 0,
open: 0,
resolved: 0,
mttr: { value: null, unavailable: true },
bySource: [],
bySeverity: [],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals-empty");
expect(screen.queryByTestId("cc-signals-pie")).toBeNull();
});
});

View File

@@ -0,0 +1,302 @@
/*
FNXC:CommandCenter 2026-06-25-00:00:
FN-7044 extracted shared Command Center area test fixtures into this harness so focused sibling suites can stay under the 2000-line hard cap without duplicating fixture logic or moving Vitest hoisted mocks.
*/
import { expect, vi } from "vitest";
import { screen, within } from "@testing-library/react";
import type { OrgTreeNode } from "@fusion/core";
import type { DateRange } from "../../DateRangePicker";
export const range7d: DateRange = { from: "2026-06-08", to: null, preset: "7d" };
export const customRange = (from: string, to: string): DateRange => ({ from, to, preset: "custom" });
export function providerIconIn(element: HTMLElement, provider: string): Element | null {
return element.querySelector(`.provider-icon[data-provider="${provider}"]`);
}
export function tokenFixture(totalTokens = 1500) {
return {
from: "2026-06-08",
to: null,
groupBy: "model",
totals: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: totalTokens > 0 ? 5 : 0,
},
cost: { usd: 12.5, unavailable: false, stale: false },
series: [
{
bucket: "2026-06-08",
inputTokens: 400,
outputTokens: 200,
cachedTokens: 0,
cacheWriteTokens: 0,
totalTokens: 600,
nTasks: 2,
cost: { usd: 4.5, unavailable: false, stale: false },
},
{
bucket: "2026-06-09",
inputTokens: 600,
outputTokens: 300,
cachedTokens: 0,
cacheWriteTokens: 0,
totalTokens: 900,
nTasks: 3,
cost: { usd: 8, unavailable: false, stale: false },
},
],
groups: [
{
key: "gpt-4o",
inputTokens: 600,
outputTokens: 300,
cachedTokens: 100,
cacheWriteTokens: 0,
totalTokens: 900,
nTasks: 3,
cost: { usd: 9.0, unavailable: false, stale: false },
},
{
key: "claude-sonnet",
inputTokens: 400,
outputTokens: 200,
cachedTokens: 100,
cacheWriteTokens: 0,
totalTokens: 600,
nTasks: 2,
cost: { usd: 3.5, unavailable: false, stale: false },
},
],
};
}
export function githubFixture() {
return {
from: "2026-06-08",
to: null,
filed: 5,
fixed: 3,
net: 2,
daily: [
{ date: "2026-06-08", filed: 2, fixed: 1 },
{ date: "2026-06-09", filed: 3, fixed: 2 },
],
byRepo: [
{ repo: "acme/alpha", filed: 4, fixed: 1 },
{ repo: "acme/beta", filed: 1, fixed: 2 },
],
resolved: [],
};
}
export function productivityFixture() {
return {
from: "2026-06-08",
to: null,
modifiedFiles: 7,
commits: 4,
pullRequests: 2,
byLanguage: [{ language: "TypeScript", count: 7 }],
loc: { value: null, unavailable: true },
hoursSaved: { value: null, unavailable: true },
taskDuration: {
completedTasks: 3,
averageMs: 90 * 60 * 1000,
medianMs: 60 * 60 * 1000,
p90Ms: 2 * 60 * 60 * 1000,
totalMs: 270 * 60 * 1000,
unavailable: false,
},
};
}
export function installElementClientWidth(width: number) {
return vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(width);
}
export function agentNode(id: string, name: string, children: OrgTreeNode[] = [], title = "Team Lead"): OrgTreeNode {
return {
agent: {
id,
name,
title,
role: "executor",
state: "idle",
createdAt: "2026-06-19T00:00:00.000Z",
updatedAt: "2026-06-19T00:00:00.000Z",
metadata: {},
},
children,
};
}
export function emptyTeamFixture() {
return {
from: null,
to: null,
totals: {
tokens: { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
cost: { usd: null, unavailable: false, stale: false },
filesChanged: 0,
tasksCompleted: 0,
tasksInProgress: 0,
tasksInReview: 0,
},
agents: [],
};
}
export function populatedTeamFixture(totalTokens = 1500) {
return {
...emptyTeamFixture(),
totals: {
tokens: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: 2,
},
cost: { usd: 4.25, unavailable: false, stale: false },
filesChanged: 7,
tasksCompleted: 3,
tasksInProgress: 1,
tasksInReview: 0,
},
agents: [
{
agentId: "agent-alpha",
agentName: "Alpha Agent",
role: "executor",
state: "running",
tokens: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: 2,
},
cost: { usd: 4.25, unavailable: false, stale: false },
filesChanged: 7,
tasksCompleted: 3,
tasksInProgress: 1,
tasksInReview: 0,
},
],
};
}
export function activityFixture() {
return {
from: "2026-06-08",
to: null,
sessions: 4,
messages: 12,
activeNodes: 3,
activeAgents: 2,
agentRuns: { total: 8, active: 1, completed: 6, failed: 1 },
daily: [
{ day: "2026-06-08", messages: 2, activeNodes: 1, activeAgents: 1, agentRuns: 2 },
{ day: "2026-06-09", messages: 4, activeNodes: 2, activeAgents: 1, agentRuns: 3 },
{ day: "2026-06-10", messages: 6, activeNodes: 3, activeAgents: 2, agentRuns: 3 },
],
stickiness: 0.5,
mttr: { value: null, unavailable: true, sampleCount: 0 },
monitor: {
mttr: { value: null, unavailable: true, sampleCount: 0 },
incidentsOpened: 0,
incidentsResolved: 0,
openIncidents: 0,
deployments: 0,
},
funnel: {
stages: [],
enteredInRange: 0,
doneInRange: 0,
completionRate: null,
throughputPerDay: 0,
rangeDays: 7,
},
};
}
export function expectRechartsWrapperWithin(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
expect(chart.classList.contains("cc-recharts-chart") || chart.classList.contains("cc-recharts-empty")).toBe(true);
expect(chart.outerHTML).not.toMatch(/NaN|Infinity/);
}
export function expectBarFillsFinite(testId: string): void {
const section = screen.getByTestId(testId);
for (const fill of Array.from(section.querySelectorAll<HTMLElement>(".cc-bar-fill"))) {
expect(fill.style.width).toMatch(/^\d+(?:\.\d+)?%$/);
expect(fill.style.width).not.toMatch(/NaN|Infinity/);
}
}
export function expectSparklineHeightsFinite(testId: string): void {
const section = screen.getByTestId(testId);
for (const bar of Array.from(section.querySelectorAll<HTMLElement>(".cc-sparkline-bar"))) {
expect(bar.style.height).toMatch(/^\d+(?:\.\d+)?%$/);
expect(bar.style.height).not.toMatch(/NaN|Infinity/);
}
}
export function viewBoxNumbers(chart: Element): [number, number, number, number] {
return (chart.getAttribute("viewBox") ?? "")
.split(/\s+/)
.map(Number) as [number, number, number, number];
}
export function expectSvgLinePointsInsideViewBox(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
const [, , viewBoxWidth, viewBoxHeight] = viewBoxNumbers(chart);
for (const point of Array.from(chart.querySelectorAll(".cc-line-chart-point"))) {
const cx = Number(point.getAttribute("cx"));
const cy = Number(point.getAttribute("cy"));
const r = Number(point.getAttribute("r"));
expect(cx).toBeGreaterThanOrEqual(r);
expect(cx).toBeLessThanOrEqual(viewBoxWidth - r);
expect(cy).toBeGreaterThanOrEqual(r);
expect(cy).toBeLessThanOrEqual(viewBoxHeight - r);
}
}
export function expectSvgLineFillsBoxAndKeepsRoundMarkers(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
const [, , viewBoxWidth, viewBoxHeight] = viewBoxNumbers(chart);
const line = chart.querySelector(".cc-line-chart-path");
const pointPairs = (line?.getAttribute("points") ?? "")
.trim()
.split(/\s+/)
.filter(Boolean)
.map((pair) => pair.split(",").map(Number) as [number, number]);
expect(viewBoxWidth).toBeGreaterThan(viewBoxHeight);
expect(chart.getAttribute("preserveAspectRatio")).toBe("none");
expect(chart.getAttribute("viewBox")).not.toBe("0 0 100 100");
expect(chart.querySelectorAll(".cc-line-chart-point").length).toBeGreaterThan(0);
expect(pointPairs[0]?.[0]).toBe(3);
expect(pointPairs.at(-1)?.[0]).toBe(viewBoxWidth - 3);
}
export function pluginActivationFixture(overrides: Partial<{ activations: number; unavailable: boolean }> = {}) {
const activations = overrides.activations ?? 0;
const unavailable = overrides.unavailable ?? true;
return {
from: "2026-06-08",
to: null,
activations,
byPlugin: unavailable ? [] : [{ pluginId: "fusion-plugin-example", count: activations }],
unavailable,
};
}

View File

@@ -1,12 +1,14 @@
/*
FNXC:CommandCenter 2026-06-16-09:42:
Command Center area component tests (PR #1683). Pin loading/error/unavailable-vs-zero rendering for each analytics area against mocked fixtures so the "—" sentinel and cost-unavailable contracts can't regress.
FNXC:CommandCenter 2026-06-25-00:00:
FN-7044 split shared fixtures into areas.test-harness.tsx so this suite can be partitioned into focused sibling files under the 2000-line hard cap without losing coverage.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor, within, act, renderHook } from "@testing-library/react";
import type { OrgTreeNode } from "@fusion/core";
// Mock the api() helper so the areas fetch deterministic fixtures.
const mocks = vi.hoisted(() => ({
@@ -50,235 +52,31 @@ vi.mock("../../../../hooks/useAppSettings", () => ({
import { TokensArea } from "../TokensArea";
import { ToolsArea } from "../ToolsArea";
import { ProductivityArea } from "../ProductivityArea";
import { GithubArea } from "../GithubArea";
import { SignalsArea } from "../SignalsArea";
import { TeamArea } from "../TeamArea";
import { ActivityArea } from "../ActivityArea";
import { EcosystemArea } from "../EcosystemArea";
import { useAnalyticsArea } from "../useAnalyticsArea";
import { ConfirmDialogProvider } from "../../../../hooks/useConfirm";
import { rangeQuery } from "../areaShared";
import { defaultPresets, rangeFromPreset, type DateRange } from "../../DateRangePicker";
const range7d: DateRange = { from: "2026-06-08", to: null, preset: "7d" };
const customRange = (from: string, to: string): DateRange => ({ from, to, preset: "custom" });
function providerIconIn(element: HTMLElement, provider: string): Element | null {
return element.querySelector(`.provider-icon[data-provider="${provider}"]`);
}
function tokenFixture(totalTokens = 1500) {
return {
from: "2026-06-08",
to: null,
groupBy: "model",
totals: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: totalTokens > 0 ? 5 : 0,
},
cost: { usd: 12.5, unavailable: false, stale: false },
series: [
{
bucket: "2026-06-08",
inputTokens: 400,
outputTokens: 200,
cachedTokens: 0,
cacheWriteTokens: 0,
totalTokens: 600,
nTasks: 2,
cost: { usd: 4.5, unavailable: false, stale: false },
},
{
bucket: "2026-06-09",
inputTokens: 600,
outputTokens: 300,
cachedTokens: 0,
cacheWriteTokens: 0,
totalTokens: 900,
nTasks: 3,
cost: { usd: 8, unavailable: false, stale: false },
},
],
groups: [
{
key: "gpt-4o",
inputTokens: 600,
outputTokens: 300,
cachedTokens: 100,
cacheWriteTokens: 0,
totalTokens: 900,
nTasks: 3,
cost: { usd: 9.0, unavailable: false, stale: false },
},
{
key: "claude-sonnet",
inputTokens: 400,
outputTokens: 200,
cachedTokens: 100,
cacheWriteTokens: 0,
totalTokens: 600,
nTasks: 2,
cost: { usd: 3.5, unavailable: false, stale: false },
},
],
};
}
function githubFixture() {
return {
from: "2026-06-08",
to: null,
filed: 5,
fixed: 3,
net: 2,
daily: [
{ date: "2026-06-08", filed: 2, fixed: 1 },
{ date: "2026-06-09", filed: 3, fixed: 2 },
],
byRepo: [
{ repo: "acme/alpha", filed: 4, fixed: 1 },
{ repo: "acme/beta", filed: 1, fixed: 2 },
],
resolved: [],
};
}
function productivityFixture() {
return {
from: "2026-06-08",
to: null,
modifiedFiles: 7,
commits: 4,
pullRequests: 2,
byLanguage: [{ language: "TypeScript", count: 7 }],
loc: { value: null, unavailable: true },
hoursSaved: { value: null, unavailable: true },
taskDuration: {
completedTasks: 3,
averageMs: 90 * 60 * 1000,
medianMs: 60 * 60 * 1000,
p90Ms: 2 * 60 * 60 * 1000,
totalMs: 270 * 60 * 1000,
unavailable: false,
},
};
}
function installElementClientWidth(width: number) {
return vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(width);
}
function agentNode(id: string, name: string, children: OrgTreeNode[] = [], title = "Team Lead"): OrgTreeNode {
return {
agent: {
id,
name,
title,
role: "executor",
state: "idle",
createdAt: "2026-06-19T00:00:00.000Z",
updatedAt: "2026-06-19T00:00:00.000Z",
metadata: {},
},
children,
};
}
function emptyTeamFixture() {
return {
from: null,
to: null,
totals: {
tokens: { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
cost: { usd: null, unavailable: false, stale: false },
filesChanged: 0,
tasksCompleted: 0,
tasksInProgress: 0,
tasksInReview: 0,
},
agents: [],
};
}
function populatedTeamFixture(totalTokens = 1500) {
return {
...emptyTeamFixture(),
totals: {
tokens: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: 2,
},
cost: { usd: 4.25, unavailable: false, stale: false },
filesChanged: 7,
tasksCompleted: 3,
tasksInProgress: 1,
tasksInReview: 0,
},
agents: [
{
agentId: "agent-alpha",
agentName: "Alpha Agent",
role: "executor",
state: "running",
tokens: {
inputTokens: Math.round(totalTokens * 0.6),
outputTokens: Math.round(totalTokens * 0.3),
cachedTokens: Math.round(totalTokens * 0.1),
cacheWriteTokens: 0,
totalTokens,
nTasks: 2,
},
cost: { usd: 4.25, unavailable: false, stale: false },
filesChanged: 7,
tasksCompleted: 3,
tasksInProgress: 1,
tasksInReview: 0,
},
],
};
}
function activityFixture() {
return {
from: "2026-06-08",
to: null,
sessions: 4,
messages: 12,
activeNodes: 3,
activeAgents: 2,
agentRuns: { total: 8, active: 1, completed: 6, failed: 1 },
daily: [
{ day: "2026-06-08", messages: 2, activeNodes: 1, activeAgents: 1, agentRuns: 2 },
{ day: "2026-06-09", messages: 4, activeNodes: 2, activeAgents: 1, agentRuns: 3 },
{ day: "2026-06-10", messages: 6, activeNodes: 3, activeAgents: 2, agentRuns: 3 },
],
stickiness: 0.5,
mttr: { value: null, unavailable: true, sampleCount: 0 },
monitor: {
mttr: { value: null, unavailable: true, sampleCount: 0 },
incidentsOpened: 0,
incidentsResolved: 0,
openIncidents: 0,
deployments: 0,
},
funnel: {
stages: [],
enteredInRange: 0,
doneInRange: 0,
completionRate: null,
throughputPerDay: 0,
rangeDays: 7,
},
};
}
import { defaultPresets, rangeFromPreset } from "../../DateRangePicker";
import {
activityFixture,
agentNode,
customRange,
emptyTeamFixture,
expectBarFillsFinite,
expectRechartsWrapperWithin,
expectSparklineHeightsFinite,
expectSvgLineFillsBoxAndKeepsRoundMarkers,
expectSvgLinePointsInsideViewBox,
installElementClientWidth,
pluginActivationFixture,
populatedTeamFixture,
productivityFixture,
providerIconIn,
range7d,
tokenFixture,
} from "./areas.test-harness";
beforeEach(() => {
apiMock.mockReset();
@@ -306,67 +104,6 @@ afterEach(() => {
vi.useRealTimers();
});
function expectRechartsWrapperWithin(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
expect(chart.classList.contains("cc-recharts-chart") || chart.classList.contains("cc-recharts-empty")).toBe(true);
expect(chart.outerHTML).not.toMatch(/NaN|Infinity/);
}
function expectBarFillsFinite(testId: string): void {
const section = screen.getByTestId(testId);
for (const fill of Array.from(section.querySelectorAll<HTMLElement>(".cc-bar-fill"))) {
expect(fill.style.width).toMatch(/^\d+(?:\.\d+)?%$/);
expect(fill.style.width).not.toMatch(/NaN|Infinity/);
}
}
function expectSparklineHeightsFinite(testId: string): void {
const section = screen.getByTestId(testId);
for (const bar of Array.from(section.querySelectorAll<HTMLElement>(".cc-sparkline-bar"))) {
expect(bar.style.height).toMatch(/^\d+(?:\.\d+)?%$/);
expect(bar.style.height).not.toMatch(/NaN|Infinity/);
}
}
function viewBoxNumbers(chart: Element): [number, number, number, number] {
return (chart.getAttribute("viewBox") ?? "")
.split(/\s+/)
.map(Number) as [number, number, number, number];
}
function expectSvgLinePointsInsideViewBox(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
const [, , viewBoxWidth, viewBoxHeight] = viewBoxNumbers(chart);
for (const point of Array.from(chart.querySelectorAll(".cc-line-chart-point"))) {
const cx = Number(point.getAttribute("cx"));
const cy = Number(point.getAttribute("cy"));
const r = Number(point.getAttribute("r"));
expect(cx).toBeGreaterThanOrEqual(r);
expect(cx).toBeLessThanOrEqual(viewBoxWidth - r);
expect(cy).toBeGreaterThanOrEqual(r);
expect(cy).toBeLessThanOrEqual(viewBoxHeight - r);
}
}
function expectSvgLineFillsBoxAndKeepsRoundMarkers(testId: string, label: string): void {
const section = screen.getByTestId(testId);
const chart = within(section).getByRole("img", { name: label });
const [, , viewBoxWidth, viewBoxHeight] = viewBoxNumbers(chart);
const line = chart.querySelector(".cc-line-chart-path");
const pointPairs = (line?.getAttribute("points") ?? "")
.trim()
.split(/\s+/)
.filter(Boolean)
.map((pair) => pair.split(",").map(Number) as [number, number]);
expect(viewBoxWidth).toBeGreaterThan(viewBoxHeight);
expect(chart.getAttribute("preserveAspectRatio")).toBe("none");
expect(chart.getAttribute("viewBox")).not.toBe("0 0 100 100");
expect(chart.querySelectorAll(".cc-line-chart-point").length).toBeGreaterThan(0);
expect(pointPairs[0]?.[0]).toBe(3);
expect(pointPairs.at(-1)?.[0]).toBe(viewBoxWidth - 3);
}
describe("rangeQuery / rangeFromPreset", () => {
it("serializes every default preset into a distinct server-resolvable query", () => {
@@ -1626,17 +1363,6 @@ describe("TeamArea", () => {
});
});
function pluginActivationFixture(overrides: Partial<{ activations: number; unavailable: boolean }> = {}) {
const activations = overrides.activations ?? 0;
const unavailable = overrides.unavailable ?? true;
return {
from: "2026-06-08",
to: null,
activations,
byPlugin: unavailable ? [] : [{ pluginId: "fusion-plugin-example", count: activations }],
unavailable,
};
}
function mockEcosystemResponses(tokens: unknown, activations: unknown): void {
apiMock.mockImplementation((path: string) => {
@@ -1735,333 +1461,3 @@ describe("EcosystemArea", () => {
});
});
describe("GithubArea", () => {
it("renders filed/fixed/net stats, daily trend, and by-repo bars", async () => {
apiMock.mockResolvedValue(githubFixture());
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-filed").textContent).toContain("5");
expect(screen.getByTestId("cc-github-fixed").textContent).toContain("3");
expect(screen.getByTestId("cc-github-net").textContent).toContain("2");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.getByTestId("cc-github-line")).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed vs fixed share" })).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed vs fixed line" })).toBeTruthy();
expect(screen.getByTestId("cc-github-daily-trend")).toBeTruthy();
expect(screen.getByRole("img", { name: "Filed" })).toBeTruthy();
expect(screen.getByRole("img", { name: "Fixed" })).toBeTruthy();
const repoChart = screen.getByRole("list", { name: "By repository" });
expect(within(repoChart).getByText("acme/alpha")).toBeTruthy();
expect(within(repoChart).getByLabelText("acme/alpha: 4 filed / 1 fixed")).toBeTruthy();
});
it("renders resolved issues with safe outbound links and approximation labels", async () => {
apiMock.mockResolvedValue({
...githubFixture(),
resolved: [
{
taskId: "FN-100",
taskTitle: "Fix alpha crash",
repo: "acme/alpha",
issueNumber: 123,
url: "https://github.com/acme/alpha/issues/123",
resolvedAt: "2026-06-10T12:34:56.000Z",
resolvedAtExact: true,
},
{
taskId: "FN-101",
taskTitle: "Patch unknown import",
repo: "(unknown)",
issueNumber: null,
url: null,
resolvedAt: "2026-06-09T08:00:00.000Z",
resolvedAtExact: false,
},
],
});
render(<GithubArea range={range7d} />);
const section = await screen.findByTestId("cc-github-resolved");
expect(section.textContent).toContain("Resolved issues");
expect(section.textContent).toContain("acme/alpha#123");
expect(section.textContent).toContain("Fix alpha crash");
expect(section.textContent).toContain("FN-100");
expect(section.textContent).toContain("(unknown)");
expect(section.textContent).toContain("Patch unknown import");
expect(section.textContent).toContain("approx");
expect(section.textContent).toContain("2026");
const linkedIssue = within(section).getByRole("link", { name: "Open GitHub issue acme/alpha#123" });
expect(linkedIssue.getAttribute("href")).toBe("https://github.com/acme/alpha/issues/123");
expect(linkedIssue.getAttribute("target")).toBe("_blank");
expect(linkedIssue.getAttribute("rel")).toBe("noopener noreferrer");
expect(within(section).queryByRole("link", { name: /unknown/i })).toBeNull();
});
it("omits the resolved issues section for an empty resolved list", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), resolved: [] });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.queryByTestId("cc-github-resolved")).toBeNull();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
});
it("renders the empty state without empty chart shells", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), filed: 0, fixed: 0, net: 0, daily: [], byRepo: [] });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-area-github").textContent).toContain("No GitHub issue activity");
expect(screen.getByTestId("cc-github-backfill-button")).toBeTruthy();
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
expect(screen.queryByTestId("cc-github-daily-trend")).toBeNull();
expect(screen.queryByTestId("cc-github-by-repo")).toBeNull();
});
it("renders loading and error states", async () => {
apiMock.mockImplementationOnce(() => new Promise(() => undefined));
const { unmount } = render(<GithubArea range={range7d} />);
expect(screen.getByTestId("cc-area-github-loading")).toBeTruthy();
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
unmount();
apiMock.mockRejectedValueOnce(new Error("github failed"));
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github-error");
expect(screen.getByTestId("cc-area-github-error").textContent).toContain("github failed");
expect(screen.queryByTestId("cc-github-pie")).toBeNull();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
});
it("handles undefined chart arrays and zero values without NaN output", async () => {
apiMock.mockResolvedValue({ ...githubFixture(), filed: 1, fixed: 0, net: 1, daily: undefined, byRepo: undefined });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.queryByTestId("cc-github-line")).toBeNull();
expect(screen.queryByTestId("cc-github-daily-trend")).toBeNull();
expect(screen.queryByTestId("cc-github-by-repo")).toBeNull();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
});
it("keeps GitHub recharts safe for single-item and non-finite daily data", async () => {
apiMock.mockResolvedValue({
...githubFixture(),
filed: 1,
fixed: 1,
net: 0,
daily: [{ date: "2026-06-08", filed: Number.NaN, fixed: Number.POSITIVE_INFINITY }],
byRepo: [{ repo: "acme/broken", filed: Number.NaN, fixed: -1 }],
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
expect(screen.getByTestId("cc-github-pie")).toBeTruthy();
expect(screen.getByTestId("cc-github-line")).toBeTruthy();
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("NaN");
expect(screen.getByTestId("cc-area-github").textContent).not.toContain("Infinity");
});
it("rejects an inverted custom range client-side without fetching", async () => {
render(<GithubArea range={customRange("2026-06-10", "2026-06-01")} />);
await waitFor(() => expect(apiMock).not.toHaveBeenCalled());
});
it("runs a single backfill batch and renders accumulated result counts", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 4,
filled: 2,
skipped: 1,
errors: 0,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
await screen.findByText(/Backfill complete/i);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledWith({ offset: 0, limit: 100 }, undefined);
const result = screen.getByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Scanned 4, filled 2, skipped 1, errors 0");
});
it("paginates multi-batch backfills with advancing offsets and summed counts", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock
.mockResolvedValueOnce({ scanned: 100, filled: 4, skipped: 90, errors: 1, hasMore: true })
.mockResolvedValueOnce({ scanned: 25, filled: 3, skipped: 22, errors: 0, hasMore: false });
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
await waitFor(() => expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(2));
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenNthCalledWith(1, { offset: 0, limit: 100 }, undefined);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenNthCalledWith(2, { offset: 100, limit: 100 }, undefined);
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Scanned 125, filled 7, skipped 112, errors 1");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("shows the all-zero backfill as nothing to backfill instead of an error", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 0,
filled: 0,
skipped: 0,
errors: 0,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("Nothing to backfill");
expect(result.className).not.toContain("cc-github-backfill-status--error");
});
it("surfaces nonzero backfill error counts without throwing", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValueOnce({
scanned: 8,
filled: 1,
skipped: 5,
errors: 2,
hasMore: false,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("errors 2");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("captures endpoint failures in local error UI", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockRejectedValueOnce(new Error("endpoint failed"));
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByTestId("cc-github-backfill-result");
expect(result.textContent).toContain("endpoint failed");
expect(result.className).toContain("cc-github-backfill-status--error");
});
it("disables and guards the button while a backfill is in flight", async () => {
apiMock.mockResolvedValue(githubFixture());
let resolveBackfill: ((value: { scanned: number; filled: number; skipped: number; errors: number; hasMore: boolean }) => void) | null = null;
backfillGithubSourceIssueClosedAtMock.mockImplementationOnce(
() => new Promise((resolve) => {
resolveBackfill = resolve;
}),
);
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
const button = screen.getByTestId("cc-github-backfill-button") as HTMLButtonElement;
fireEvent.click(button);
await waitFor(() => expect(button.disabled).toBe(true));
fireEvent.click(button);
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(1);
resolveBackfill?.({ scanned: 1, filled: 1, skipped: 0, errors: 0, hasMore: false });
await screen.findByText(/Backfill complete/i);
});
it("stops a pathological always-has-more response at the max iteration guard", async () => {
apiMock.mockResolvedValue(githubFixture());
backfillGithubSourceIssueClosedAtMock.mockResolvedValue({
scanned: 1,
filled: 0,
skipped: 1,
errors: 0,
hasMore: true,
});
render(<GithubArea range={range7d} />);
await screen.findByTestId("cc-area-github");
fireEvent.click(screen.getByTestId("cc-github-backfill-button"));
const result = await screen.findByText(/safety limit/i);
expect(result.textContent).toContain("safety limit");
expect(backfillGithubSourceIssueClosedAtMock).toHaveBeenCalledTimes(1000);
expect(screen.getByTestId("cc-github-backfill-result").textContent).toContain("Scanned 1000");
});
});
// FN-6684 Mission Control decision: no extra pie/line test here because MissionControlPanel already renders the live SDLC Funnel for its only quantitative distribution; adding a pie would duplicate that affordance.
describe("SignalsArea", () => {
it("renders the empty state (not an error) when the signals endpoint is missing", async () => {
apiMock.mockRejectedValue(new Error("API returned HTML instead of JSON (404)"));
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals-empty");
// Must not surface the error UI.
expect(screen.queryByTestId("cc-area-signals-error")).toBeNull();
expect(screen.queryByTestId("cc-signals-pie")).toBeNull();
});
it("renders signal metrics and status pie when data is present", async () => {
apiMock.mockResolvedValue({
totalSignals: 8,
open: 3,
resolved: 5,
mttr: { value: 42, unavailable: false },
bySource: [{ source: "sentry", count: 8 }],
bySeverity: [{ severity: "error", count: 8 }],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals");
expect(screen.getByTestId("cc-signals-total").textContent).toContain("8");
expect(screen.getByTestId("cc-signals-mttr").textContent).toContain("42");
expect(screen.getByTestId("cc-signals-pie")).toBeTruthy();
expect(screen.getByRole("img", { name: "Signal status share" })).toBeTruthy();
});
it("keeps signals pie safe for single-item and non-finite source/severity data", async () => {
apiMock.mockResolvedValue({
totalSignals: 1,
open: 1,
resolved: 0,
mttr: { value: null, unavailable: true },
bySource: [{ source: "broken", count: Number.NaN }],
bySeverity: [{ severity: "broken", count: Number.POSITIVE_INFINITY }],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals");
expect(screen.getByTestId("cc-signals-pie")).toBeTruthy();
expect(screen.getByTestId("cc-area-signals").textContent).not.toContain("NaN");
expect(screen.getByTestId("cc-area-signals").textContent).not.toContain("Infinity");
});
it("renders settled zero signals without a pie shell", async () => {
apiMock.mockResolvedValue({
totalSignals: 0,
open: 0,
resolved: 0,
mttr: { value: null, unavailable: true },
bySource: [],
bySeverity: [],
});
render(<SignalsArea range={range7d} />);
await screen.findByTestId("cc-area-signals-empty");
expect(screen.queryByTestId("cc-signals-pie")).toBeNull();
});
});