| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- import { fireEvent, render, screen, within } from "@testing-library/react";
- import { describe, it, expect, vi } from "vitest";
- import userEvent from "@testing-library/user-event";
- import toast from "#/utils/toast";
- import ChatMessage from "#/components/chat/ChatMessage";
- describe("Message", () => {
- it("should render a user message", () => {
- render(
- <ChatMessage
- message={{
- sender: "user",
- content: "Hello",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- />,
- );
- expect(screen.getByTestId("article")).toBeInTheDocument();
- expect(screen.getByTestId("article")).toHaveClass("self-end"); // user message should be on the right side
- });
- it("should render an assistant message", () => {
- render(
- <ChatMessage
- message={{
- sender: "assistant",
- content: "Hi",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- />,
- );
- expect(screen.getByTestId("article")).toBeInTheDocument();
- expect(screen.getByTestId("article")).not.toHaveClass("self-end"); // assistant message should be on the left side
- });
- it("should render markdown content", () => {
- render(
- <ChatMessage
- message={{
- sender: "user",
- content: "```js\nconsole.log('Hello')\n```",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- />,
- );
- // SyntaxHighlighter breaks the code blocks into "tokens"
- expect(screen.getByText("console")).toBeInTheDocument();
- expect(screen.getByText("log")).toBeInTheDocument();
- expect(screen.getByText("'Hello'")).toBeInTheDocument();
- });
- describe("copy to clipboard", () => {
- const toastInfoSpy = vi.spyOn(toast, "info");
- const toastErrorSpy = vi.spyOn(toast, "error");
- it("should copy any message to clipboard", async () => {
- const user = userEvent.setup();
- render(
- <ChatMessage
- message={{
- sender: "user",
- content: "Hello",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- />,
- );
- const message = screen.getByTestId("article");
- let copyButton = within(message).queryByTestId("copy-button");
- expect(copyButton).not.toBeInTheDocument();
- // I am using `fireEvent` here because `userEvent.hover()` seems to interfere with the
- // `userEvent.click()` call later on
- fireEvent.mouseEnter(message);
- copyButton = within(message).getByTestId("copy-button");
- await user.click(copyButton);
- expect(navigator.clipboard.readText()).resolves.toBe("Hello");
- expect(toastInfoSpy).toHaveBeenCalled();
- });
- it("should show an error message when the message cannot be copied", async () => {
- const user = userEvent.setup();
- render(
- <ChatMessage
- message={{
- sender: "user",
- content: "Hello",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- />,
- );
- const message = screen.getByTestId("article");
- fireEvent.mouseEnter(message);
- const copyButton = within(message).getByTestId("copy-button");
- const clipboardSpy = vi
- .spyOn(navigator.clipboard, "writeText")
- .mockRejectedValue(new Error("Failed to copy"));
- await user.click(copyButton);
- expect(clipboardSpy).toHaveBeenCalled();
- expect(toastErrorSpy).toHaveBeenCalled();
- });
- });
- describe("confirmation buttons", () => {
- const expectButtonsNotToBeRendered = () => {
- expect(
- screen.queryByTestId("action-confirm-button"),
- ).not.toBeInTheDocument();
- expect(
- screen.queryByTestId("action-reject-button"),
- ).not.toBeInTheDocument();
- };
- it.skip("should display confirmation buttons for the last assistant message", () => {
- // it should not render buttons if the message is not the last one
- const { rerender } = render(
- <ChatMessage
- message={{
- sender: "assistant",
- content: "Are you sure?",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage={false}
- awaitingUserConfirmation
- />,
- );
- expectButtonsNotToBeRendered();
- // it should not render buttons if the message is not from the assistant
- rerender(
- <ChatMessage
- message={{
- sender: "user",
- content: "Yes",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage
- awaitingUserConfirmation
- />,
- );
- expectButtonsNotToBeRendered();
- // it should not render buttons if the message is not awaiting user confirmation
- rerender(
- <ChatMessage
- message={{
- sender: "assistant",
- content: "Are you sure?",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage
- awaitingUserConfirmation={false}
- />,
- );
- expectButtonsNotToBeRendered();
- // it should render buttons if all conditions are met
- rerender(
- <ChatMessage
- message={{
- sender: "assistant",
- content: "Are you sure?",
- imageUrls: [],
- timestamp: new Date().toISOString(),
- }}
- isLastMessage
- awaitingUserConfirmation
- />,
- );
- const confirmButton = screen.getByTestId("action-confirm-button");
- const rejectButton = screen.getByTestId("action-reject-button");
- expect(confirmButton).toBeInTheDocument();
- expect(rejectButton).toBeInTheDocument();
- });
- });
- });
|