## 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 -->
133 lines
4.4 KiB
TypeScript
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();
|
|
});
|
|
});
|