Files
kandev/apps/web/hooks/use-task-removal-session-loading.test.ts
T
2026-08-17 11:14:12 +00:00

105 lines
4.2 KiB
TypeScript

import { beforeEach, describe, expect, it, onTestFinished, vi } from "vitest";
import { renderHook } from "@testing-library/react";
import type { StoreApi } from "zustand";
import type { AppState } from "@/lib/state/store";
import type { TaskSession } from "@/lib/types/http";
const listTaskSessionsMock = vi.fn();
const TASK_ID = "task-cached";
vi.mock("@/lib/api", () => ({
fetchTask: vi.fn(),
listTaskSessions: (...args: unknown[]) => listTaskSessionsMock(...args),
}));
import { useTaskRemoval } from "./use-task-removal";
function makeSession(id: string): TaskSession {
return {
id,
task_id: TASK_ID,
task_environment_id: `${id}-environment`,
state: "WAITING_FOR_INPUT",
started_at: "2026-08-15T15:00:00Z",
updated_at: "2026-08-15T15:00:00Z",
} as TaskSession;
}
function makeStore(cachedSession: TaskSession): StoreApi<AppState> {
const state = {
taskSessionsByTask: {
itemsByTaskId: { [TASK_ID]: [cachedSession] },
loadedByTaskId: { [TASK_ID]: true },
loadingByTaskId: {},
},
setTaskSessionsLoading: vi.fn(),
setTaskSessionsForTask: vi.fn(),
} as unknown as AppState;
return {
getState: () => state,
setState: vi.fn(),
subscribe: vi.fn(),
} as unknown as StoreApi<AppState>;
}
describe("useTaskRemoval session loading", () => {
beforeEach(() => vi.clearAllMocks());
it("force-refreshes a previously loaded task session list", async () => {
const freshSession = makeSession("fresh-session");
listTaskSessionsMock.mockResolvedValue({ sessions: [freshSession] });
const { result } = renderHook(() =>
useTaskRemoval({ store: makeStore(makeSession("cached-session")) }),
);
const sessions = await result.current.loadTaskSessionsForTask(TASK_ID, { force: true });
expect(listTaskSessionsMock).toHaveBeenCalledWith(TASK_ID, { cache: "no-store" });
expect(sessions).toEqual([freshSession]);
});
it("rejects a failed forced refresh instead of returning a stale owner", async () => {
listTaskSessionsMock.mockRejectedValue(new Error("offline"));
const { result } = renderHook(() =>
useTaskRemoval({ store: makeStore(makeSession("cached-session")) }),
);
await expect(result.current.loadTaskSessionsForTask(TASK_ID, { force: true })).rejects.toThrow(
"offline",
);
});
it("rejects an older forced response after a newer snapshot wins", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
onTestFinished(() => consoleError.mockRestore());
type SessionResponse = { sessions: TaskSession[] };
let resolveOlder: (response: SessionResponse) => void = () => {};
let resolveNewer: (response: SessionResponse) => void = () => {};
const olderResponse = new Promise<SessionResponse>((resolve) => {
resolveOlder = resolve;
});
const newerResponse = new Promise<SessionResponse>((resolve) => {
resolveNewer = resolve;
});
listTaskSessionsMock.mockReturnValueOnce(olderResponse).mockReturnValueOnce(newerResponse);
const store = makeStore(makeSession("cached-session"));
const { result } = renderHook(() => useTaskRemoval({ store }));
const olderLoad = result.current.loadTaskSessionsForTask(TASK_ID, { force: true });
const newerLoad = result.current.loadTaskSessionsForTask(TASK_ID, { force: true });
const newerSession = makeSession("newer-session");
resolveNewer({ sessions: [newerSession] });
await expect(newerLoad).resolves.toEqual([newerSession]);
resolveOlder({ sessions: [makeSession("older-session")] });
await expect(olderLoad).rejects.toMatchObject({ name: "AbortError" });
expect(consoleError).not.toHaveBeenCalled();
expect(store.getState().setTaskSessionsForTask).toHaveBeenCalledTimes(1);
expect(store.getState().setTaskSessionsForTask).toHaveBeenCalledWith(TASK_ID, [newerSession]);
expect(store.getState().setTaskSessionsLoading).toHaveBeenNthCalledWith(1, TASK_ID, true);
expect(store.getState().setTaskSessionsLoading).toHaveBeenNthCalledWith(2, TASK_ID, true);
expect(store.getState().setTaskSessionsLoading).toHaveBeenNthCalledWith(3, TASK_ID, false);
expect(store.getState().setTaskSessionsLoading).toHaveBeenCalledTimes(3);
});
});