Files
fusion/packages/dashboard/app/components/__tests__/TaskCard.cli-states.test.tsx
gsxdsm 478f226a54 test: green full-suite CI after main drift (#2229)
## Summary
Restores green **Full Suite (non-blocking)** runs on `main`. Recent main
merges left i18n key parity, schema baseline bookkeeping (0011→0012),
heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger
whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out
of date, so all four test shards failed.

## Root causes observed on main
- **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` +
`reviewBudgetExhausted` in non-en locales; orphan
`awaitingApprovalPlanReviewReplanCap`
- **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to
`0012` while tests still equated it with
`OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from
applied-migration lists
- **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for
`fn_task_logs_read`; merger tests still mocked `git diff-tree` for
trivial classification after the execFile `:2:`/`:3:` cutover; mock
provider `updateTask` arity drift

## Changes
- Locale catalogs: add missing keys, drop orphan key
- Schema applier tests: immutable 0011 identity + baseline 0012 lists
- Heartbeat + gating snapshots: include `fn_task_logs_read`
- Merger unit mocks: recognize `git diff -p -w :2:path :3:path`
- Mock provider: accept optional third `updateTask` arg

## Test plan
- [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/postgres/schema-applier.test.ts` (immutable + automation
upgrade) — pass
- [x] `pnpm --filter @fusion/core exec vitest run` project-identity +
satellite-fusiondir — pass
- [x] Engine suites from failed CI shards (file-scoped,
hermes/openclaw/paperclip/grok, reliability post-finalize/mission,
heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass
- [ ] Full Suite workflow green on merge to main

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Improved project data isolation across backend operations.
- Added safer optional toast handling when UI components render outside
the full application shell.
  - Added support for reading task logs during agent heartbeat sessions.

- **Bug Fixes**
- Prevented runtime probes from hanging and avoided scanning large
binary files.
  - Improved path handling for workspaces with missing descendants.
- Corrected task retry state resets and GitHub import/issue-close
behavior.

- **Style**
  - Improved chat, terminal, and settings spacing.
  - Added clearer accessibility labeling for the auto-merge control.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 16:01:29 -07:00

133 lines
4.4 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", () => ({
useOptionalToast: () => null,
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", () => ({
fetchWorkflowSettingValues: vi.fn(async () => ({ stored: {}, effective: {}, orphaned: [] })),
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();
});
});