|
import { render, screen, within } from "@testing-library/react";
|
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
QueryClientProvider,
|
|
QueryClient,
|
|
QueryClientConfig,
|
|
} from "@tanstack/react-query";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { createRoutesStub } from "react-router";
|
|
import { ConversationPanel } from "#/components/features/conversation-panel/conversation-panel";
|
|
import OpenHands from "#/api/open-hands";
|
|
import { AuthProvider } from "#/context/auth-context";
|
|
import { clickOnEditButton } from "./utils";
|
|
|
|
describe("ConversationPanel", () => {
|
|
const onCloseMock = vi.fn();
|
|
const RouterStub = createRoutesStub([
|
|
{
|
|
Component: () => <ConversationPanel onClose={onCloseMock} />,
|
|
path: "/",
|
|
},
|
|
]);
|
|
|
|
const renderConversationPanel = (config?: QueryClientConfig) =>
|
|
render(<RouterStub />, {
|
|
wrapper: ({ children }) => (
|
|
<AuthProvider>
|
|
<QueryClientProvider client={new QueryClient(config)}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
</AuthProvider>
|
|
),
|
|
});
|
|
|
|
const { endSessionMock } = vi.hoisted(() => ({
|
|
endSessionMock: vi.fn(),
|
|
}));
|
|
|
|
beforeAll(() => {
|
|
vi.mock("react-router", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("react-router")>()),
|
|
Link: ({ children }: React.PropsWithChildren) => children,
|
|
useNavigate: vi.fn(() => vi.fn()),
|
|
useLocation: vi.fn(() => ({ pathname: "/conversation" })),
|
|
useParams: vi.fn(() => ({ conversationId: "2" })),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-end-session", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("#/hooks/use-end-session")>()),
|
|
useEndSession: vi.fn(() => endSessionMock),
|
|
}));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should render the conversations", async () => {
|
|
renderConversationPanel();
|
|
const cards = await screen.findAllByTestId("conversation-card");
|
|
|
|
|
|
|
|
expect(cards).toHaveLength(3);
|
|
});
|
|
|
|
it("should display an empty state when there are no conversations", async () => {
|
|
const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations");
|
|
getUserConversationsSpy.mockResolvedValue([]);
|
|
|
|
renderConversationPanel();
|
|
|
|
const emptyState = await screen.findByText("CONVERSATION$NO_CONVERSATIONS");
|
|
expect(emptyState).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle an error when fetching conversations", async () => {
|
|
const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations");
|
|
getUserConversationsSpy.mockRejectedValue(
|
|
new Error("Failed to fetch conversations"),
|
|
);
|
|
|
|
renderConversationPanel({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
const error = await screen.findByText("Failed to fetch conversations");
|
|
expect(error).toBeInTheDocument();
|
|
});
|
|
|
|
it("should cancel deleting a conversation", async () => {
|
|
const user = userEvent.setup();
|
|
renderConversationPanel();
|
|
|
|
let cards = await screen.findAllByTestId("conversation-card");
|
|
expect(
|
|
within(cards[0]).queryByTestId("delete-button"),
|
|
).not.toBeInTheDocument();
|
|
|
|
const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
|
|
await user.click(ellipsisButton);
|
|
const deleteButton = screen.getByTestId("delete-button");
|
|
|
|
|
|
await user.click(deleteButton);
|
|
|
|
|
|
const cancelButton = screen.getByText("Cancel");
|
|
await user.click(cancelButton);
|
|
|
|
expect(screen.queryByText("Cancel")).not.toBeInTheDocument();
|
|
|
|
|
|
cards = await screen.findAllByTestId("conversation-card");
|
|
expect(cards).toHaveLength(3);
|
|
});
|
|
|
|
it("should call endSession after deleting a conversation that is the current session", async () => {
|
|
const user = userEvent.setup();
|
|
renderConversationPanel();
|
|
|
|
let cards = await screen.findAllByTestId("conversation-card");
|
|
const ellipsisButton = within(cards[1]).getByTestId("ellipsis-button");
|
|
await user.click(ellipsisButton);
|
|
const deleteButton = screen.getByTestId("delete-button");
|
|
|
|
|
|
await user.click(deleteButton);
|
|
|
|
|
|
const confirmButton = screen.getByText("Confirm");
|
|
await user.click(confirmButton);
|
|
|
|
expect(screen.queryByText("Confirm")).not.toBeInTheDocument();
|
|
|
|
|
|
cards = await screen.findAllByTestId("conversation-card");
|
|
expect(cards).toHaveLength(2);
|
|
|
|
expect(endSessionMock).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("should delete a conversation", async () => {
|
|
const user = userEvent.setup();
|
|
renderConversationPanel();
|
|
|
|
let cards = await screen.findAllByTestId("conversation-card");
|
|
const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
|
|
await user.click(ellipsisButton);
|
|
const deleteButton = screen.getByTestId("delete-button");
|
|
|
|
|
|
await user.click(deleteButton);
|
|
|
|
|
|
const confirmButton = screen.getByText("Confirm");
|
|
await user.click(confirmButton);
|
|
|
|
expect(screen.queryByText("Confirm")).not.toBeInTheDocument();
|
|
|
|
|
|
cards = await screen.findAllByTestId("conversation-card");
|
|
expect(cards).toHaveLength(1);
|
|
});
|
|
|
|
it("should rename a conversation", async () => {
|
|
const updateUserConversationSpy = vi.spyOn(
|
|
OpenHands,
|
|
"updateUserConversation",
|
|
);
|
|
|
|
const user = userEvent.setup();
|
|
renderConversationPanel();
|
|
const cards = await screen.findAllByTestId("conversation-card");
|
|
const title = within(cards[0]).getByTestId("conversation-card-title");
|
|
|
|
await clickOnEditButton(user);
|
|
|
|
await user.clear(title);
|
|
await user.type(title, "Conversation 1 Renamed");
|
|
await user.tab();
|
|
|
|
|
|
expect(updateUserConversationSpy).toHaveBeenCalledWith("3", {
|
|
title: "Conversation 1 Renamed",
|
|
});
|
|
});
|
|
|
|
it("should not rename a conversation when the name is unchanged", async () => {
|
|
const updateUserConversationSpy = vi.spyOn(
|
|
OpenHands,
|
|
"updateUserConversation",
|
|
);
|
|
|
|
const user = userEvent.setup();
|
|
renderConversationPanel();
|
|
const cards = await screen.findAllByTestId("conversation-card");
|
|
const title = within(cards[0]).getByTestId("conversation-card-title");
|
|
|
|
await user.click(title);
|
|
await user.tab();
|
|
|
|
|
|
expect(updateUserConversationSpy).not.toHaveBeenCalled();
|
|
|
|
await clickOnEditButton(user);
|
|
|
|
await user.type(title, "Conversation 1");
|
|
await user.click(title);
|
|
await user.tab();
|
|
|
|
expect(updateUserConversationSpy).toHaveBeenCalledTimes(1);
|
|
|
|
await user.click(title);
|
|
await user.tab();
|
|
|
|
expect(updateUserConversationSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should call onClose after clicking a card", async () => {
|
|
renderConversationPanel();
|
|
const cards = await screen.findAllByTestId("conversation-card");
|
|
const firstCard = cards[0];
|
|
|
|
await userEvent.click(firstCard);
|
|
|
|
expect(onCloseMock).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|