import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import type { Task } from "@fusion/core"; import { TaskCard } from "../TaskCard"; import { TaskChangesTab } from "../TaskChangesTab"; const useTaskDiffStatsMock = vi.fn(); const fetchTaskDiffMock = vi.fn(); const fetchTaskCommitAssociationsMock = vi.fn(); vi.mock("../../hooks/useTaskDiffStats", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTaskDiffStats: (...args: unknown[]) => useTaskDiffStatsMock(...args), }; }); vi.mock("../../api", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, fetchTaskDiff: (...args: unknown[]) => fetchTaskDiffMock(...args), fetchTaskCommitAssociations: (...args: unknown[]) => fetchTaskCommitAssociationsMock(...args), }; }); vi.mock("lucide-react", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, Link: () => null, GitBranch: () => null, Clock: () => null, Pencil: () => null, Layers: () => null, ChevronDown: () => null, Folder: () => null, GitPullRequest: () => null, CircleDot: () => null, Target: () => null, Bot: () => null, Trash2: () => null, RotateCw: () => null, Zap: () => null, FileCode: () => null, ChevronRight: () => null, ChevronLeft: () => null, AlertCircle: () => null, GitCommit: () => null, WrapText: () => null, Maximize2: () => null, }; }); vi.mock("../ProviderIcon", () => ({ ProviderIcon: () => null })); vi.mock("../ChangesDiffModal", () => ({ ChangesDiffModal: () => null })); vi.mock("../../hooks/useBadgeWebSocket", () => ({ useBadgeWebSocket: () => ({ badgeUpdates: new Map(), isConnected: true, subscribeToBadge: vi.fn(), unsubscribeFromBadge: vi.fn() }), })); vi.mock("../../hooks/useBatchBadgeFetch", () => ({ getFreshBatchData: vi.fn(() => null) })); vi.mock("../../utils/highlightDiff", () => ({ highlightDiff: (diff: string) => diff })); vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn() }) })); function makeTask(overrides: Partial): Task { return { id: "FN-4521", title: "Parity", description: "", column: "in-progress", dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-05-14T00:00:00.000Z", updatedAt: "2026-05-14T00:00:00.000Z", columnMovedAt: "2026-05-14T00:00:00.000Z", ...overrides, } as Task; } describe("TaskCard/TaskChangesTab files-changed parity", () => { beforeEach(() => { useTaskDiffStatsMock.mockReset(); fetchTaskDiffMock.mockReset(); fetchTaskCommitAssociationsMock.mockReset(); fetchTaskCommitAssociationsMock.mockResolvedValue({ taskId: "FN-4521", lineageId: null, associations: [] }); }); it.each([ ["in-progress", undefined, undefined], ["in-review", undefined, undefined], ["done", undefined, { commitSha: "abc123" }], ] as const)("keeps counts aligned for %s", async (column, worktree, mergeDetails) => { const stats = { filesChanged: 3, additions: 5, deletions: 2 }; useTaskDiffStatsMock.mockReturnValue({ stats, loading: false }); fetchTaskDiffMock.mockResolvedValue({ files: [ { path: "a.ts", status: "modified", additions: 1, deletions: 0, patch: "@@" }, { path: "b.ts", status: "modified", additions: 1, deletions: 1, patch: "@@" }, { path: "c.ts", status: "added", additions: 3, deletions: 1, patch: "@@" }, ], stats, }); const task = makeTask({ column, worktree, mergeDetails }); const { container } = render( <> {}} addToast={() => {}} /> , ); await waitFor(() => { expect(screen.getByText(/Files Changed \(3\)/)).toBeTruthy(); }); expect(container.textContent).toContain("3 files changed"); }); });