## Summary Fixes all remaining full-suite failures from run 29231108919 (FN-7923–7931 drift) + expands the structural mock-completeness guard to cover dashboard tests. ## Fixes ### Dashboard (shard 4) — 4 files - **`TaskCard.cli-states.test.tsx`** — Proxy lucide-react mock needed `has`/`getOwnPropertyDescriptor` traps; TaskCard now imports `priorityIndicator.tsx` which reads `ArrowDown` at module-init. Vitest validates ESM named exports via `in`/descriptor, not `get`. Also added `useToast` mock. - **`SettingsModalNodeRouting.test.tsx`** — Pass `initialSection="node-routing"` (it's in `ADVANCED_SETTINGS_SECTION_IDS`, nav hidden by default). - **`styles-css-rgba-tokenization.test.ts`** — Removed stale `.settings-sidebar` color-mix expectation (FN-7825 made it structural-only). - **`mcp-helper-forwarding.test.ts`** — Added `resolvePlanningThinkingLevel` to `@fusion/engine` mock (insight extraction calls it before MCP forwarding). ### Structural guard expansion - **`.tsx` blind spot fixed** — `collectTs` and test file filter now include `.tsx` files - **Shorthand property extraction** — key extractor now matches both `key: value` and `key,` (shorthand) - **Convention mapping** — `.test.tsx → .tsx` source resolution added - **Dashboard test coverage** — `@fusion/engine` barrel check now scans `packages/dashboard/src/__tests__/` - **7 latent mock gaps completed** — `pr-conflict-resolver`, `project-pause-resume-routes`, `routes-approval-sandbox-provisioning`, `routes-approval`, `routes-worktrunk`, `session-reconnect`, `setup-routes` ### Engine (shards 1+2) — zero real failures All 3 failing files are local-only (`@agentclientprotocol/sdk` + pi-ai staleness). CI resolves them from lockfile. ## Verification - Gate (with expanded guard): exit 0 ✅ - Dashboard (5 non-local files): 36/36 passed ✅ - 3 files skipped locally (`@agentclientprotocol/sdk`) — CI will verify <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved test reliability by completing module mocks across dashboard and routing scenarios. * Updated settings and task card test coverage to reflect current UI behavior. * Enhanced mock validation to cover additional test files, TypeScript React files, and shorthand exports. * Prevented failures related to missing providers, engine helpers, and planning configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
import React from "react";
|
|
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { TaskCard, type CliCardState } from "../TaskCard";
|
|
import type { Task } from "@fusion/core";
|
|
|
|
vi.mock("lucide-react", () => {
|
|
const Stub = () => null;
|
|
// FNXC:DashboardMocks 2026-07-13-14:00 (round 11):
|
|
// TaskCard now imports priorityIndicator.tsx, which reads ArrowDown/ArrowUp/Flag/TriangleAlert
|
|
// from lucide-react at module-init time. A get-only Proxy is not enough: Vitest validates named
|
|
// ESM exports via `in`/`getOwnPropertyDescriptor` before reading the binding, so a bare Proxy
|
|
// over {} reports no exports and throws "No ArrowDown export is defined on the lucide-react mock".
|
|
// Expose every string key as an own enumerable Stub so any icon resolves to a null stub.
|
|
return new Proxy({}, {
|
|
get: (_target, prop) => prop === "then" ? undefined : Stub,
|
|
has: (_target, prop) => typeof prop === "string" && prop !== "then",
|
|
getOwnPropertyDescriptor: (_target, prop) =>
|
|
typeof prop === "string" && prop !== "then"
|
|
? { configurable: true, enumerable: true, value: Stub, writable: true }
|
|
: undefined,
|
|
});
|
|
});
|
|
// FNXC:DashboardMocks 2026-07-13-14:00 (round 11):
|
|
// TaskCard embeds RuntimeFallbackBadge, which calls the shared useToast() hook
|
|
// directly (not the addToast prop). This file renders <TaskCard> outside a
|
|
// ToastProvider, so mock the hook to avoid "useToast must be used within
|
|
// ToastProvider", matching the TaskCard.test.tsx pattern.
|
|
vi.mock("../../hooks/useToast", () => ({
|
|
useToast: () => ({
|
|
addToast: vi.fn(),
|
|
removeToast: vi.fn(),
|
|
toasts: [],
|
|
}),
|
|
}));
|
|
|
|
vi.mock("../ProviderIcon", () => ({
|
|
ProviderIcon: ({ provider }: { provider: string }) => <span data-testid={`provider-icon-${provider}`} />,
|
|
}));
|
|
|
|
vi.mock("../../hooks/useTaskDiffStats", () => ({
|
|
useTaskDiffStats: () => ({ stats: null, loading: false }),
|
|
}));
|
|
|
|
const badgeUpdatesMock = new Map<string, unknown>();
|
|
vi.mock("../../hooks/useBadgeWebSocket", () => ({
|
|
useBadgeWebSocket: () => ({
|
|
badgeUpdates: badgeUpdatesMock,
|
|
isConnected: true,
|
|
subscribeToBadge: vi.fn(),
|
|
unsubscribeFromBadge: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("../../hooks/useBatchBadgeFetch", () => ({
|
|
getFreshBatchData: vi.fn(() => null),
|
|
}));
|
|
|
|
vi.mock("../../api", () => ({
|
|
fetchTaskDetail: vi.fn(),
|
|
uploadAttachment: vi.fn(),
|
|
fetchMission: vi.fn(),
|
|
fetchAgent: vi.fn(),
|
|
fetchAgents: vi.fn(),
|
|
rebuildTaskSpec: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../../hooks/useConfirm", () => ({
|
|
useConfirm: () => ({ confirm: vi.fn(), confirmWithChoice: vi.fn() }),
|
|
}));
|
|
|
|
function makeTask(overrides: Partial<Task> = {}): Task {
|
|
return {
|
|
id: "FN-001",
|
|
title: "Test task",
|
|
column: "in-progress",
|
|
status: undefined as never,
|
|
steps: [],
|
|
dependencies: [],
|
|
description: "",
|
|
...overrides,
|
|
} as Task;
|
|
}
|
|
|
|
const noop = () => {};
|
|
|
|
function renderCard(cliSessionState?: CliCardState) {
|
|
return render(
|
|
<TaskCard
|
|
task={makeTask()}
|
|
onOpenDetail={noop}
|
|
addToast={noop}
|
|
cliSessionState={cliSessionState}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
afterEach(() => {
|
|
badgeUpdatesMock.clear();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("TaskCard CLI agent state badges (U11)", () => {
|
|
it("renders the waiting-on-input badge when the session is waitingOnInput", () => {
|
|
renderCard({ agentState: "waitingOnInput" });
|
|
const badge = screen.getByText("Waiting on input");
|
|
expect(badge).toBeTruthy();
|
|
expect(badge.getAttribute("data-cli-state")).toBe("waitingOnInput");
|
|
});
|
|
|
|
it("renders the needs-attention badge when the session needsAttention", () => {
|
|
renderCard({ agentState: "needsAttention" });
|
|
const badge = screen.getByText("Needs attention");
|
|
expect(badge).toBeTruthy();
|
|
expect(badge.getAttribute("data-cli-state")).toBe("needsAttention");
|
|
});
|
|
|
|
it("busy clears both CLI badges (F2 — answering re-arms to busy)", () => {
|
|
renderCard({ agentState: "busy" });
|
|
expect(screen.queryByText("Waiting on input")).toBeNull();
|
|
expect(screen.queryByText("Needs attention")).toBeNull();
|
|
});
|
|
|
|
it("no cli session → no CLI badges (card unchanged)", () => {
|
|
renderCard(undefined);
|
|
expect(screen.queryByText("Waiting on input")).toBeNull();
|
|
expect(screen.queryByText("Needs attention")).toBeNull();
|
|
});
|
|
});
|