Files
fusion/packages/dashboard/app/hooks/__tests__/useSessionFiles.test.ts
gsxdsm 9d5b0865b8 feat(FN-1544): add viewport-gated loading and lightweight memo comparison
- Add viewport-gated fetching to TaskCard component so only visible cards load their data
- Implement lightweight memo comparison to prevent unnecessary re-renders
- Add lazy enable gates to useSessionFiles and useTaskDiffStats hooks with caching
- Add comprehensive tests for useSessionFiles and useTaskDiffStats hooks
- Document viewport-gated loading patterns in memory and dashboard-load performance docs
2026-04-10 10:44:48 -07:00

124 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { useSessionFiles } from "../useSessionFiles";
import * as api from "../../api";
vi.mock("../../api", () => ({
fetchSessionFiles: vi.fn(),
}));
const mockFetchSessionFiles = vi.mocked(api.fetchSessionFiles);
describe("useSessionFiles", () => {
beforeEach(() => {
mockFetchSessionFiles.mockReset();
});
afterEach(() => {
vi.clearAllMocks();
});
it("fetches session files for active tasks with a worktree", async () => {
mockFetchSessionFiles.mockResolvedValueOnce(["src/a.ts", "src/b.ts"]);
const { result } = renderHook(() => useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-progress"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual(["src/a.ts", "src/b.ts"]);
expect(mockFetchSessionFiles).toHaveBeenCalledWith("FN-123", undefined);
});
it("does not fetch for tasks without worktrees or inactive columns", async () => {
const { result: noWorktree } = renderHook(() => useSessionFiles("FN-123", undefined, "in-progress"));
const { result: inactive } = renderHook(() => useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "todo"));
await waitFor(() => expect(noWorktree.current.loading).toBe(false));
await waitFor(() => expect(inactive.current.loading).toBe(false));
expect(noWorktree.current.files).toEqual([]);
expect(inactive.current.files).toEqual([]);
expect(mockFetchSessionFiles).not.toHaveBeenCalled();
});
it("returns empty files on fetch failure", async () => {
mockFetchSessionFiles.mockRejectedValueOnce(new Error("boom"));
const { result } = renderHook(() => useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-review"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual([]);
});
it("fetches session files for done column tasks with a worktree", async () => {
mockFetchSessionFiles.mockResolvedValueOnce(["src/x.ts", "src/y.ts", "src/z.ts"]);
const { result } = renderHook(() => useSessionFiles("FN-456", "/repo/.worktrees/kb-456", "done"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual(["src/x.ts", "src/y.ts", "src/z.ts"]);
expect(mockFetchSessionFiles).toHaveBeenCalledWith("FN-456", undefined);
});
it("does not fetch for done column tasks without a worktree", async () => {
const { result } = renderHook(() => useSessionFiles("FN-456", undefined, "done"));
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual([]);
expect(mockFetchSessionFiles).not.toHaveBeenCalled();
});
describe("enabled option", () => {
it("fetches when enabled is true (default)", async () => {
mockFetchSessionFiles.mockResolvedValueOnce(["file.ts"]);
const { result } = renderHook(() =>
useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-progress", undefined, { enabled: true }),
);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual(["file.ts"]);
expect(mockFetchSessionFiles).toHaveBeenCalled();
});
it("fetches when enabled is not specified (default)", async () => {
mockFetchSessionFiles.mockResolvedValueOnce(["file.ts"]);
const { result } = renderHook(() =>
useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-progress"),
);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual(["file.ts"]);
expect(mockFetchSessionFiles).toHaveBeenCalled();
});
it("does not fetch when enabled is false", async () => {
const { result } = renderHook(() =>
useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-progress", undefined, { enabled: false }),
);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual([]);
expect(mockFetchSessionFiles).not.toHaveBeenCalled();
});
it("returns stable state (loading: false) when disabled", async () => {
const { result } = renderHook(() =>
useSessionFiles("FN-123", "/repo/.worktrees/kb-123", "in-progress", undefined, { enabled: false }),
);
// Immediately check (before any async)
expect(result.current.loading).toBe(false);
expect(result.current.files).toEqual([]);
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.files).toEqual([]);
expect(mockFetchSessionFiles).not.toHaveBeenCalled();
});
});
});