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();
});
});
});