import { defineComponent } from "vue";
import { render, screen, fireEvent, waitFor } from "@testing-library/vue";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { AssistantMessage, UserMessage } from "@ag-ui/core";
import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
import CopilotChatAssistantMessage from "../CopilotChatAssistantMessage.vue";
import CopilotChatUserMessage from "../CopilotChatUserMessage.vue";
const TEST_THREAD_ID = "test-thread";
const createAssistantMessage = (content: string): AssistantMessage => ({
id: "msg-assistant-1",
role: "assistant",
content,
});
const createUserMessage = (content: string): UserMessage => ({
id: "msg-user-1",
role: "user",
content,
});
const renderAssistantMessage = (message: AssistantMessage) => {
const Host = defineComponent({
components: {
CopilotKitProvider,
CopilotChatConfigurationProvider,
CopilotChatAssistantMessage,
},
setup() {
return { message, TEST_THREAD_ID };
},
template: `
`,
});
return render(Host);
};
const renderUserMessage = (message: UserMessage) => {
const Host = defineComponent({
components: {
CopilotKitProvider,
CopilotChatConfigurationProvider,
CopilotChatUserMessage,
},
setup() {
return { message, TEST_THREAD_ID };
},
template: `
`,
});
return render(Host);
};
describe("CopyButton clipboard behavior", () => {
let originalClipboard: Clipboard | undefined;
beforeEach(() => {
originalClipboard = navigator.clipboard;
});
afterEach(() => {
Object.defineProperty(navigator, "clipboard", {
value: originalClipboard,
writable: true,
configurable: true,
});
});
describe("AssistantMessage CopyButton", () => {
it("shows copied state only after successful clipboard write", async () => {
const writeTextMock = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
renderAssistantMessage(createAssistantMessage("Hello assistant"));
const copyButton = screen.getByTestId("copilot-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(writeTextMock).toHaveBeenCalledWith("Hello assistant");
});
await waitFor(() => {
expect(copyButton.querySelector(".lucide-check")).not.toBeNull();
});
});
it("does NOT show copied state when clipboard API is unavailable", async () => {
Object.defineProperty(navigator, "clipboard", {
value: undefined,
writable: true,
configurable: true,
});
renderAssistantMessage(createAssistantMessage("Hello assistant"));
const copyButton = screen.getByTestId("copilot-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(copyButton.querySelector(".lucide-check")).toBeNull();
expect(copyButton.querySelector(".lucide-copy")).not.toBeNull();
});
});
it("logs error when clipboard write rejects", async () => {
const writeTextMock = vi
.fn()
.mockRejectedValue(new Error("Permission denied"));
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
const consoleSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});
renderAssistantMessage(createAssistantMessage("Hello assistant"));
const copyButton = screen.getByTestId("copilot-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(writeTextMock).toHaveBeenCalledWith("Hello assistant");
});
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith(
"Failed to copy to clipboard:",
expect.any(Error),
);
});
expect(copyButton.querySelector(".lucide-check")).toBeNull();
expect(copyButton.querySelector(".lucide-copy")).not.toBeNull();
consoleSpy.mockRestore();
});
});
describe("UserMessage CopyButton", () => {
it("shows copied state only after successful clipboard write", async () => {
const writeTextMock = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
renderUserMessage(createUserMessage("Hello user"));
const copyButton = screen.getByTestId("copilot-user-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(writeTextMock).toHaveBeenCalledWith("Hello user");
});
await waitFor(() => {
expect(copyButton.querySelector(".lucide-check")).not.toBeNull();
});
});
it("does NOT show copied state when clipboard API is unavailable", async () => {
Object.defineProperty(navigator, "clipboard", {
value: undefined,
writable: true,
configurable: true,
});
renderUserMessage(createUserMessage("Hello user"));
const copyButton = screen.getByTestId("copilot-user-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(copyButton.querySelector(".lucide-check")).toBeNull();
expect(copyButton.querySelector(".lucide-copy")).not.toBeNull();
});
});
it("logs error when clipboard write rejects", async () => {
const writeTextMock = vi
.fn()
.mockRejectedValue(new Error("Permission denied"));
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
const consoleSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});
renderUserMessage(createUserMessage("Hello user"));
const copyButton = screen.getByTestId("copilot-user-copy-button");
await fireEvent.click(copyButton);
await waitFor(() => {
expect(writeTextMock).toHaveBeenCalledWith("Hello user");
});
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith(
"Failed to copy to clipboard:",
expect.any(Error),
);
});
expect(copyButton.querySelector(".lucide-check")).toBeNull();
expect(copyButton.querySelector(".lucide-copy")).not.toBeNull();
consoleSpy.mockRestore();
});
});
});