import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { render } from "@testing-library/react"; import type { Task } from "@fusion/core"; import { TaskCard } from "../TaskCard"; import { loadAllAppCss } from "../../test/cssFixture"; vi.mock("lucide-react", () => ({ Link: () => , GitBranch: () => , Clock: () => , Pencil: () => , Layers: () => , ChevronDown: () => , Folder: () => , GitPullRequest: () => , CircleDot: () => , CheckCircle2: () => , XCircle: () => , Target: () => , Bot: () => , Trash2: () => , RotateCw: () => , Zap: () => , AlertTriangle: () => , })); vi.mock("../ProviderIcon", () => ({ ProviderIcon: () => , })); vi.mock("../PluginSlot", () => ({ PluginSlot: () => null, })); vi.mock("../../hooks/useTaskDiffStats", () => ({ useTaskDiffStats: () => ({ stats: null, loading: false }), })); 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("../../api", () => ({ fetchTaskDetail: vi.fn(), uploadAttachment: vi.fn(), fetchMission: vi.fn(), fetchAgent: vi.fn(), })); vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn(async () => true) }), })); const noop = () => {}; function makeTask(overrides: Partial = {}): Task { return { id: "FN-5210", title: "Wrap footer chips when the card gets narrow", description: "", column: "in-progress", status: "executing" as Task["status"], steps: [], dependencies: [], sourceType: "dashboard_ui", githubTracking: { enabled: true, issue: { owner: "runfusion", repo: "fusion", number: 5210, url: "https://github.com/runfusion/fusion/issues/5210", createdAt: "2026-05-19T12:00:00.000Z", }, }, retrySummary: { total: 3 } as Task["retrySummary"], executionStartedAt: "2026-05-19T12:00:00.000Z", updatedAt: "2026-05-19T12:05:00.000Z", ...overrides, } as Task; } describe("TaskCard footer wrapping (FN-5210)", () => { let cleanupCss: (() => void) | undefined; beforeEach(async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-05-19T12:10:00.000Z")); const style = document.createElement("style"); style.textContent = await Promise.resolve(loadAllAppCss()); document.head.appendChild(style); cleanupCss = () => style.remove(); }); afterEach(() => { cleanupCss?.(); cleanupCss = undefined; vi.useRealTimers(); }); it("FN-5210 wraps the footer row and right chip cluster with a non-zero row gap", () => { const { container } = render( , ); const footerRow = container.querySelector(".card-footer-row") as HTMLElement; const rightCluster = container.querySelector(".card-footer-row-right") as HTMLElement; const retryChip = container.querySelector(".card-retry-badge") as HTMLElement; const githubChip = container.querySelector(".card-github-tracking-chip") as HTMLElement; const timeChip = container.querySelector(".card-time-indicator") as HTMLElement; expect(footerRow).toBeTruthy(); expect(rightCluster).toBeTruthy(); expect(retryChip).toBeTruthy(); expect(githubChip).toBeTruthy(); expect(timeChip).toBeTruthy(); const footerStyles = getComputedStyle(footerRow); expect(footerStyles.flexWrap).toBe("wrap"); expect(footerStyles.rowGap).toMatch(/^(var\(--space-xs\)|(?!0(?:px)?$)\d+(?:\.\d+)?px)$/); const rightClusterStyles = getComputedStyle(rightCluster); expect(rightClusterStyles.flexWrap).toBe("wrap"); expect(rightClusterStyles.rowGap).toMatch(/^(var\(--space-xs\)|(?!0(?:px)?$)\d+(?:\.\d+)?px)$/); expect(rightClusterStyles.justifyContent).toBe("flex-end"); }); it.each([ ".card-retry-badge", ".card-github-tracking-chip", ".card-time-indicator", ])("FN-5210 keeps %s internally nowrap so wrapping happens at chip boundaries", (selector) => { const { container } = render( , ); const chip = container.querySelector(selector) as HTMLElement; expect(chip, `${selector} should render for the FN-5210 fixture`).toBeTruthy(); const styles = getComputedStyle(chip); expect(styles.whiteSpace).toBe("nowrap"); expect(styles.flexShrink).toBe("0"); }); });