test(FN-4308): complete Step 4 — add card and modal aggregation regressions
Fusion-Task-Id: FN-4308 Fusion-Task-Lineage: d0a46a8f-2245-4ddc-99b4-06d78e87db16
This commit is contained in:
@@ -25,6 +25,11 @@ vi.mock("../ProviderIcon", () => ({
|
|||||||
ProviderIcon: ({ provider }: { provider: string }) => <span data-testid={`provider-icon-${provider}`} />,
|
ProviderIcon: ({ provider }: { provider: string }) => <span data-testid={`provider-icon-${provider}`} />,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const useTaskDiffStatsMock = vi.fn(() => ({ stats: null, loading: false }));
|
||||||
|
vi.mock("../../hooks/useTaskDiffStats", () => ({
|
||||||
|
useTaskDiffStats: (...args: any[]) => useTaskDiffStatsMock(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock the api module
|
// Mock the api module
|
||||||
vi.mock("../../api", () => ({
|
vi.mock("../../api", () => ({
|
||||||
fetchTaskDetail: vi.fn(),
|
fetchTaskDetail: vi.fn(),
|
||||||
@@ -78,6 +83,7 @@ const highFanout = {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
useTaskDiffStatsMock.mockReturnValue({ stats: null, loading: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("TaskCard", () => {
|
describe("TaskCard", () => {
|
||||||
@@ -1453,6 +1459,35 @@ describe("TaskCard", () => {
|
|||||||
expect(container.querySelector(".card-time-indicator")).not.toBeNull();
|
expect(container.querySelector(".card-time-indicator")).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers done-task /diff filesChanged over mergeDetails.filesChanged", () => {
|
||||||
|
useTaskDiffStatsMock.mockReturnValue({
|
||||||
|
stats: { filesChanged: 4, additions: 10, deletions: 2 },
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskCard
|
||||||
|
task={makeTask({
|
||||||
|
column: "done",
|
||||||
|
mergeDetails: {
|
||||||
|
commitSha: "abc123",
|
||||||
|
filesChanged: 1,
|
||||||
|
insertions: 10,
|
||||||
|
deletions: 2,
|
||||||
|
mergedAt: "2026-04-25T15:00:00.000Z",
|
||||||
|
mergeConfirmed: true,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
onOpenDetail={noop}
|
||||||
|
addToast={noop}
|
||||||
|
onOpenDetailWithTab={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: "4 files changed" })).toBeDefined();
|
||||||
|
expect(screen.queryByRole("button", { name: "1 file changed" })).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders files-changed metadata and timer chip in footer row", () => {
|
it("renders files-changed metadata and timer chip in footer row", () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<TaskCard
|
<TaskCard
|
||||||
|
|||||||
@@ -246,6 +246,35 @@ describe("TaskChangesTab — commit-backed (done tasks)", () => {
|
|||||||
expect(mockFetchTaskDiff).toHaveBeenCalledWith("FN-001", undefined, undefined);
|
expect(mockFetchTaskDiff).toHaveBeenCalledWith("FN-001", undefined, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders aggregated done-task file union and matching header count", async () => {
|
||||||
|
mockFetchTaskDiff.mockResolvedValue({
|
||||||
|
files: [
|
||||||
|
{ path: "a.ts", status: "modified", additions: 1, deletions: 0, patch: "@@" },
|
||||||
|
{ path: "b.ts", status: "added", additions: 2, deletions: 0, patch: "@@" },
|
||||||
|
{ path: "c.ts", status: "deleted", additions: 0, deletions: 1, patch: "@@" },
|
||||||
|
{ path: "d.ts", status: "modified", additions: 3, deletions: 2, patch: "@@" },
|
||||||
|
],
|
||||||
|
stats: { filesChanged: 4, additions: 6, deletions: 3 },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskChangesTab
|
||||||
|
taskId="FN-001"
|
||||||
|
worktree={undefined}
|
||||||
|
column="done"
|
||||||
|
mergeDetails={MERGE_DETAILS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText("Files Changed (4)")).toBeTruthy();
|
||||||
|
});
|
||||||
|
expect(screen.getByText("a.ts")).toBeTruthy();
|
||||||
|
expect(screen.getByText("b.ts")).toBeTruthy();
|
||||||
|
expect(screen.getByText("c.ts")).toBeTruthy();
|
||||||
|
expect(screen.getByText("d.ts")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("shows commit metadata for done task", async () => {
|
it("shows commit metadata for done task", async () => {
|
||||||
mockFetchTaskDiff.mockResolvedValue(DONE_TASK_DIFF);
|
mockFetchTaskDiff.mockResolvedValue(DONE_TASK_DIFF);
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ export function __test_clearDiffStatsCache(): void {
|
|||||||
* Fetches diff stats for a task's Changes tab.
|
* Fetches diff stats for a task's Changes tab.
|
||||||
*
|
*
|
||||||
* For active worktree-backed tasks, this keeps the TaskCard count aligned with
|
* For active worktree-backed tasks, this keeps the TaskCard count aligned with
|
||||||
* the Changes tab. For done tasks, it uses the same endpoint so the card does
|
* the Changes tab. For done tasks, it always uses `/api/tasks/:id/diff`, whose
|
||||||
* not fall back to `mergeDetails.filesChanged`, which is computed at merge time
|
* server-side aggregation is the canonical source (including multi-commit
|
||||||
* and can differ from the endpoint's count.
|
* lineage unions), instead of trusting `mergeDetails.filesChanged`.
|
||||||
*
|
*
|
||||||
* @param taskId - Task identifier
|
* @param taskId - Task identifier
|
||||||
* @param column - Current task column
|
* @param column - Current task column
|
||||||
|
|||||||
Reference in New Issue
Block a user