Files
kandev/apps/web/components/task/mobile/session-mobile-layout.test.tsx
T

142 lines
4.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import type { OpenFileTab } from "@/lib/types/backend";
vi.mock("@/components/toast-provider", () => ({
useToast: () => ({ toast: vi.fn() }),
}));
const fetchAndOpenFileMock = vi.fn();
vi.mock("../file-browser-hooks", () => ({
fetchAndOpenFile: (...args: unknown[]) => fetchAndOpenFileMock(...args),
}));
import { useMobilePanelHandlers } from "./session-mobile-layout";
const MOCK_FILE: OpenFileTab = {
path: "src/foo.ts",
name: "foo.ts",
content: "export const x = 1;",
originalContent: "export const x = 1;",
originalHash: "abc123",
isDirty: false,
};
const OTHER_FILE: OpenFileTab = {
...MOCK_FILE,
path: "src/bar.ts",
name: "bar.ts",
};
const CHAT_LINK_PATH = "src/chat-link.ts";
function renderHandlers(initialSid: string | null = "s1") {
const handlePanelChange = vi.fn();
const view = renderHook(
({ sid }) => useMobilePanelHandlers({ effectiveSessionId: sid, handlePanelChange }),
{ initialProps: { sid: initialSid } },
);
return { handlePanelChange, ...view };
}
describe("useMobilePanelHandlers", () => {
beforeEach(() => {
fetchAndOpenFileMock.mockReset();
});
it("handleOpenFile sets selectedFile and switches to files panel", () => {
const { result, handlePanelChange } = renderHandlers();
expect(result.current.selectedFile).toBeNull();
act(() => result.current.handleOpenFile(MOCK_FILE));
expect(result.current.selectedFile).toEqual(MOCK_FILE);
expect(handlePanelChange).toHaveBeenCalledWith("files");
});
it("handleOpenFileFromChat fetches and opens the viewer panel", () => {
const { result, handlePanelChange } = renderHandlers();
act(() => result.current.handleOpenFileFromChat(CHAT_LINK_PATH));
expect(fetchAndOpenFileMock).toHaveBeenCalledWith(
"s1",
CHAT_LINK_PATH,
expect.any(Function),
expect.any(Function),
);
const openFile = fetchAndOpenFileMock.mock.calls[0]?.[2] as (file: OpenFileTab) => void;
act(() => openFile(MOCK_FILE));
expect(result.current.selectedFile).toEqual(MOCK_FILE);
expect(handlePanelChange).toHaveBeenCalledWith("files");
});
it("handleOpenFileFromChat no-ops when no active session", () => {
const { result } = renderHandlers(null);
act(() => result.current.handleOpenFileFromChat(CHAT_LINK_PATH));
expect(fetchAndOpenFileMock).not.toHaveBeenCalled();
expect(result.current.selectedFile).toBeNull();
});
it("handlePanelChangeAndClearSheet clears the viewer when switching panels", () => {
const { result, handlePanelChange } = renderHandlers();
act(() => result.current.handleOpenFile(MOCK_FILE));
expect(result.current.selectedFile).toEqual(MOCK_FILE);
act(() => result.current.handlePanelChangeAndClearSheet("plan"));
expect(result.current.selectedFile).toBeNull();
expect(handlePanelChange).toHaveBeenCalledWith("plan");
});
it("clears selectedFile when effectiveSessionId changes", () => {
const { result, rerender } = renderHandlers();
act(() => result.current.handleOpenFile(MOCK_FILE));
expect(result.current.selectedFile).toEqual(MOCK_FILE);
rerender({ sid: "s2" });
expect(result.current.selectedFile).toBeNull();
});
it("keeps selectedFile when rerendered with same session", () => {
const { result, rerender } = renderHandlers();
act(() => result.current.handleOpenFile(MOCK_FILE));
rerender({ sid: "s1" });
expect(result.current.selectedFile).toEqual(MOCK_FILE);
});
it("rejects stale handleOpenFileFromChat callback after session change", () => {
const { result, rerender } = renderHandlers();
act(() => result.current.handleOpenFileFromChat(CHAT_LINK_PATH));
expect(fetchAndOpenFileMock).toHaveBeenCalledWith(
"s1",
CHAT_LINK_PATH,
expect.any(Function),
expect.any(Function),
);
// Simulate session switch before the async callback fires
rerender({ sid: "s2" });
expect(result.current.selectedFile).toBeNull();
// Invoke the stale callback that was registered for session s1
const staleCallback = fetchAndOpenFileMock.mock.calls[0]?.[2] as (file: OpenFileTab) => void;
act(() => staleCallback(MOCK_FILE));
// Should still be null because the callback belongs to the old session
expect(result.current.selectedFile).toBeNull();
});
it("latest handleOpenFile call wins", () => {
const { result } = renderHandlers();
act(() => {
result.current.handleOpenFile(MOCK_FILE);
result.current.handleOpenFile(OTHER_FILE);
});
expect(result.current.selectedFile).toEqual(OTHER_FILE);
});
});