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( , ); 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( , ); 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( , ); // 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( , ); 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( , ); 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( , ); expectButtonsNotToBeRendered(); // it should not render buttons if the message is not from the assistant rerender( , ); expectButtonsNotToBeRendered(); // it should not render buttons if the message is not awaiting user confirmation rerender( , ); expectButtonsNotToBeRendered(); // it should render buttons if all conditions are met rerender( , ); const confirmButton = screen.getByTestId("action-confirm-button"); const rejectButton = screen.getByTestId("action-reject-button"); expect(confirmButton).toBeInTheDocument(); expect(rejectButton).toBeInTheDocument(); }); }); });