mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-22 05:23:10 +08:00
feat: add bulk delete for session manager (#1693)
* feat: add bulk delete for session manager * fix: address batch delete review issues * fix: keep session list in sync after batch delete
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
} from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
|
||||
import { sessionsApi } from "@/lib/api/sessions";
|
||||
import type { SessionMessage, SessionMeta } from "@/types";
|
||||
import { setSessionFixtures } from "../msw/state";
|
||||
|
||||
@@ -62,16 +64,19 @@ const renderPage = () => {
|
||||
},
|
||||
});
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={client}>
|
||||
<SessionManagerPage appId="codex" />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
return {
|
||||
client,
|
||||
...render(
|
||||
<QueryClientProvider client={client}>
|
||||
<SessionManagerPage appId="codex" />
|
||||
</QueryClientProvider>,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const openSearch = () => {
|
||||
const searchButton = Array.from(screen.getAllByRole("button")).find((button) =>
|
||||
button.querySelector(".lucide-search"),
|
||||
const searchButton = Array.from(screen.getAllByRole("button")).find(
|
||||
(button) => button.querySelector(".lucide-search"),
|
||||
);
|
||||
|
||||
if (!searchButton) {
|
||||
@@ -81,10 +86,23 @@ const openSearch = () => {
|
||||
fireEvent.click(searchButton);
|
||||
};
|
||||
|
||||
const closeSearch = () => {
|
||||
const closeButton = Array.from(screen.getAllByRole("button")).find(
|
||||
(button) => button.querySelector(".lucide-x"),
|
||||
);
|
||||
|
||||
if (!closeButton) {
|
||||
throw new Error("Search close button not found");
|
||||
}
|
||||
|
||||
fireEvent.click(closeButton);
|
||||
};
|
||||
|
||||
describe("SessionManagerPage", () => {
|
||||
beforeEach(() => {
|
||||
toastSuccessMock.mockReset();
|
||||
toastErrorMock.mockReset();
|
||||
Element.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
const sessions: SessionMeta[] = [
|
||||
{
|
||||
@@ -178,11 +196,136 @@ describe("SessionManagerPage", () => {
|
||||
expect(screen.queryByText("Alpha Session")).not.toBeInTheDocument(),
|
||||
);
|
||||
|
||||
expect(screen.getByText("sessionManager.selectSession")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("sessionManager.selectSession"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText("sessionManager.emptySession"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(toastErrorMock).not.toHaveBeenCalled();
|
||||
expect(toastSuccessMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("restores batch delete controls when deleteMany rejects", async () => {
|
||||
const deleteManySpy = vi
|
||||
.spyOn(sessionsApi, "deleteMany")
|
||||
.mockRejectedValueOnce(new Error("network error"));
|
||||
|
||||
renderPage();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Alpha Session" }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /批量管理/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /全选当前/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /批量删除/i }));
|
||||
|
||||
const dialog = screen.getByTestId("confirm-dialog");
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /删除所选会话/i }),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toastErrorMock).toHaveBeenCalledWith("network error"),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: /批量删除/i }),
|
||||
).not.toBeDisabled(),
|
||||
);
|
||||
|
||||
deleteManySpy.mockRestore();
|
||||
});
|
||||
|
||||
it("keeps the exit batch mode button visible when search hides all sessions", async () => {
|
||||
renderPage();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Alpha Session" }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /批量管理/i }));
|
||||
openSearch();
|
||||
fireEvent.change(screen.getByRole("textbox"), {
|
||||
target: { value: "NoSuchSession" },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.queryByText("Alpha Session")).toBeNull());
|
||||
|
||||
expect(screen.getByRole("button", { name: /退出批量管理/i })).toBeVisible();
|
||||
});
|
||||
|
||||
it("drops hidden selections when search narrows the result set", async () => {
|
||||
renderPage();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Alpha Session" }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /批量管理/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /全选当前/i }));
|
||||
|
||||
expect(screen.getByText("已选 2 项")).toBeInTheDocument();
|
||||
|
||||
openSearch();
|
||||
fireEvent.change(screen.getByRole("textbox"), {
|
||||
target: { value: "Alpha" },
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText("Beta Session")).not.toBeInTheDocument(),
|
||||
);
|
||||
|
||||
closeSearch();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("已选 1 项")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("removes successfully deleted sessions from the UI before refetch completes", async () => {
|
||||
const view = renderPage();
|
||||
let resolveInvalidate!: () => void;
|
||||
const invalidateSpy = vi
|
||||
.spyOn(view.client, "invalidateQueries")
|
||||
.mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveInvalidate = () => resolve(undefined);
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Alpha Session" }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /批量管理/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /全选当前/i }));
|
||||
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.queryByText("Beta Session")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
resolveInvalidate();
|
||||
});
|
||||
invalidateSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user