Files
fusion/packages/dashboard/app/hooks/__tests__/useChangedFiles.test.ts
gsxdsm e21ea42d45 feat(KB-651): add changed files diff viewer for tasks
- Add backend API endpoint to get changed files and diffs for a task worktree
- Add useChangedFiles hook for fetching and managing diff data
- Create ChangedFilesModal component with file list and diff viewer
- Integrate modal into TaskCard with click handler to view changes
- Add frontend API integration and type definitions
- Include unit tests for hook and modal components
- Add changeset for the new feature
- Remove deprecated mission-related test files
2026-04-01 02:01:31 -07:00

80 lines
3.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, waitFor, act } from "@testing-library/react";
import { useChangedFiles } from "../useChangedFiles";
import * as api from "../../api";
vi.mock("../../api", () => ({
fetchTaskFileDiffs: vi.fn(),
}));
const mockFetchTaskFileDiffs = vi.mocked(api.fetchTaskFileDiffs);
describe("useChangedFiles", () => {
beforeEach(() => {
mockFetchTaskFileDiffs.mockReset();
});
afterEach(() => {
vi.clearAllMocks();
});
it("fetches changed files for active tasks with a worktree and auto-selects first file", async () => {
mockFetchTaskFileDiffs.mockResolvedValueOnce([
{ path: "src/a.ts", status: "modified", diff: "diff --git a/src/a.ts b/src/a.ts" },
{ path: "src/b.ts", status: "added", diff: "diff --git a/src/b.ts b/src/b.ts" },
]);
const { result } = renderHook(() => useChangedFiles("KB-651", "/repo/.worktrees/kb-651", "in-progress"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.error).toBeNull();
expect(result.current.files).toHaveLength(2);
expect(result.current.selectedFile?.path).toBe("src/a.ts");
expect(mockFetchTaskFileDiffs).toHaveBeenCalledWith("KB-651");
});
it("does not fetch for tasks without worktrees or inactive columns", async () => {
const { result: noWorktree } = renderHook(() => useChangedFiles("KB-651", undefined, "in-progress"));
const { result: inactive } = renderHook(() => useChangedFiles("KB-651", "/repo/.worktrees/kb-651", "todo"));
await waitFor(() => expect(noWorktree.current.loading).toBe(false));
await waitFor(() => expect(inactive.current.loading).toBe(false));
expect(noWorktree.current.files).toEqual([]);
expect(noWorktree.current.selectedFile).toBeNull();
expect(inactive.current.files).toEqual([]);
expect(inactive.current.selectedFile).toBeNull();
expect(mockFetchTaskFileDiffs).not.toHaveBeenCalled();
});
it("returns an error state on fetch failure", async () => {
mockFetchTaskFileDiffs.mockRejectedValueOnce(new Error("boom"));
const { result } = renderHook(() => useChangedFiles("KB-651", "/repo/.worktrees/kb-651", "in-review"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual([]);
expect(result.current.selectedFile).toBeNull();
expect(result.current.error).toBe("boom");
});
it("allows selecting a different file after data loads", async () => {
mockFetchTaskFileDiffs.mockResolvedValueOnce([
{ path: "src/a.ts", status: "modified", diff: "first" },
{ path: "src/b.ts", status: "added", diff: "second" },
]);
const { result } = renderHook(() => useChangedFiles("KB-651", "/repo/.worktrees/kb-651", "in-progress"));
await waitFor(() => expect(result.current.loading).toBe(false));
act(() => {
result.current.setSelectedFile(result.current.files[1]!);
});
expect(result.current.selectedFile?.path).toBe("src/b.ts");
});
});