Files
cc-switch/tests/components/SessionManagerPage.test.tsx
T
Jason 273a756869 fix: sync session search index with query data to refresh list after deletion
Replace useRef+useEffect async index rebuild with useMemo so the
FlexSearch index and the sessions array always reference the same data.
This ensures filtered search results update immediately when a session
is deleted via TanStack Query setQueryData.
2026-03-10 23:06:45 +08:00

189 lines
5.0 KiB
TypeScript

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
fireEvent,
render,
screen,
waitFor,
within,
} from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
import type { SessionMessage, SessionMeta } from "@/types";
import { setSessionFixtures } from "../msw/state";
const toastSuccessMock = vi.fn();
const toastErrorMock = vi.fn();
vi.mock("sonner", () => ({
toast: {
success: (...args: unknown[]) => toastSuccessMock(...args),
error: (...args: unknown[]) => toastErrorMock(...args),
},
}));
vi.mock("@/components/sessions/SessionToc", () => ({
SessionTocSidebar: () => null,
SessionTocDialog: () => null,
}));
vi.mock("@/components/ConfirmDialog", () => ({
ConfirmDialog: ({
isOpen,
title,
message,
confirmText,
cancelText,
onConfirm,
onCancel,
}: {
isOpen: boolean;
title: string;
message: string;
confirmText: string;
cancelText: string;
onConfirm: () => void;
onCancel: () => void;
}) =>
isOpen ? (
<div data-testid="confirm-dialog">
<div>{title}</div>
<div>{message}</div>
<button onClick={onConfirm}>{confirmText}</button>
<button onClick={onCancel}>{cancelText}</button>
</div>
) : null,
}));
const renderPage = () => {
const client = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
return render(
<QueryClientProvider client={client}>
<SessionManagerPage appId="codex" />
</QueryClientProvider>,
);
};
const openSearch = () => {
const searchButton = Array.from(screen.getAllByRole("button")).find((button) =>
button.querySelector(".lucide-search"),
);
if (!searchButton) {
throw new Error("Search button not found");
}
fireEvent.click(searchButton);
};
describe("SessionManagerPage", () => {
beforeEach(() => {
toastSuccessMock.mockReset();
toastErrorMock.mockReset();
const sessions: SessionMeta[] = [
{
providerId: "codex",
sessionId: "codex-session-1",
title: "Alpha Session",
summary: "Alpha summary",
projectDir: "/mock/codex",
createdAt: 2,
lastActiveAt: 20,
sourcePath: "/mock/codex/session-1.jsonl",
resumeCommand: "codex resume codex-session-1",
},
{
providerId: "codex",
sessionId: "codex-session-2",
title: "Beta Session",
summary: "Beta summary",
projectDir: "/mock/codex",
createdAt: 1,
lastActiveAt: 10,
sourcePath: "/mock/codex/session-2.jsonl",
resumeCommand: "codex resume codex-session-2",
},
];
const messages: Record<string, SessionMessage[]> = {
"codex:/mock/codex/session-1.jsonl": [
{ role: "user", content: "alpha", ts: 20 },
],
"codex:/mock/codex/session-2.jsonl": [
{ role: "user", content: "beta", ts: 10 },
],
};
setSessionFixtures(sessions, messages);
});
it("deletes the selected session and selects the next visible session", async () => {
renderPage();
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "Alpha Session" }),
).toBeInTheDocument(),
);
fireEvent.click(screen.getByRole("button", { name: /删除会话/i }));
const dialog = screen.getByTestId("confirm-dialog");
expect(dialog).toBeInTheDocument();
expect(within(dialog).getByText(/Alpha Session/)).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("button", { name: /删除会话/i }));
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "Beta Session" }),
).toBeInTheDocument(),
);
expect(screen.queryByText("Alpha Session")).not.toBeInTheDocument();
expect(toastErrorMock).not.toHaveBeenCalled();
expect(toastSuccessMock).toHaveBeenCalled();
});
it("removes a deleted session from filtered search results", async () => {
renderPage();
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "Alpha Session" }),
).toBeInTheDocument(),
);
openSearch();
fireEvent.change(screen.getByRole("textbox"), {
target: { value: "Alpha" },
});
await waitFor(() =>
expect(screen.getAllByText("Alpha Session")).toHaveLength(2),
);
fireEvent.click(screen.getByRole("button", { name: /删除会话/i }));
const dialog = screen.getByTestId("confirm-dialog");
fireEvent.click(within(dialog).getByRole("button", { name: /删除会话/i }));
await waitFor(() =>
expect(screen.queryByText("Alpha Session")).not.toBeInTheDocument(),
);
expect(screen.getByText("sessionManager.selectSession")).toBeInTheDocument();
expect(
screen.queryByText("sessionManager.emptySession"),
).not.toBeInTheDocument();
expect(toastErrorMock).not.toHaveBeenCalled();
expect(toastSuccessMock).toHaveBeenCalled();
});
});