@copilotkit/react-core 1.55.3-canary.1776215089 → 1.55.3-canary.1776243725

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ import React from "react";
2
+ import { render, screen, fireEvent, waitFor } from "@testing-library/react";
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
4
+ import { CopilotChatAssistantMessage } from "../CopilotChatAssistantMessage";
5
+ import { CopilotChatUserMessage } from "../CopilotChatUserMessage";
6
+ import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
7
+ import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
8
+ import { AssistantMessage, UserMessage } from "@ag-ui/core";
9
+
10
+ const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
11
+ <CopilotKitProvider>
12
+ <CopilotChatConfigurationProvider threadId="test-thread">
13
+ {children}
14
+ </CopilotChatConfigurationProvider>
15
+ </CopilotKitProvider>
16
+ );
17
+
18
+ const createAssistantMessage = (content: string): AssistantMessage => ({
19
+ id: "msg-assistant-1",
20
+ role: "assistant",
21
+ content,
22
+ });
23
+
24
+ const createUserMessage = (content: string): UserMessage => ({
25
+ id: "msg-user-1",
26
+ role: "user",
27
+ content,
28
+ });
29
+
30
+ describe("CopyButton clipboard behavior", () => {
31
+ let originalClipboard: Clipboard;
32
+
33
+ beforeEach(() => {
34
+ originalClipboard = navigator.clipboard;
35
+ });
36
+
37
+ afterEach(() => {
38
+ Object.defineProperty(navigator, "clipboard", {
39
+ value: originalClipboard,
40
+ writable: true,
41
+ configurable: true,
42
+ });
43
+ });
44
+
45
+ describe("AssistantMessage CopyButton", () => {
46
+ it("shows copied state only after successful clipboard write", async () => {
47
+ const writeTextMock = vi.fn().mockResolvedValue(undefined);
48
+ Object.defineProperty(navigator, "clipboard", {
49
+ value: { writeText: writeTextMock },
50
+ writable: true,
51
+ configurable: true,
52
+ });
53
+
54
+ const message = createAssistantMessage("Hello assistant");
55
+ render(
56
+ <TestWrapper>
57
+ <CopilotChatAssistantMessage message={message} />
58
+ </TestWrapper>,
59
+ );
60
+
61
+ const copyButton = screen.getByTestId("copilot-copy-button");
62
+ fireEvent.click(copyButton);
63
+
64
+ await waitFor(() => {
65
+ expect(writeTextMock).toHaveBeenCalledWith("Hello assistant");
66
+ });
67
+
68
+ // After successful write, should show check icon
69
+ await waitFor(() => {
70
+ const checkIcon = copyButton.querySelector(".lucide-check");
71
+ expect(checkIcon).not.toBeNull();
72
+ });
73
+ });
74
+
75
+ it("does NOT show copied state when clipboard API is unavailable", async () => {
76
+ Object.defineProperty(navigator, "clipboard", {
77
+ value: undefined,
78
+ writable: true,
79
+ configurable: true,
80
+ });
81
+
82
+ const message = createAssistantMessage("Hello assistant");
83
+ render(
84
+ <TestWrapper>
85
+ <CopilotChatAssistantMessage message={message} />
86
+ </TestWrapper>,
87
+ );
88
+
89
+ const copyButton = screen.getByTestId("copilot-copy-button");
90
+ fireEvent.click(copyButton);
91
+
92
+ // Wait a tick for any async handlers
93
+ await new Promise((r) => setTimeout(r, 50));
94
+
95
+ // Should still show copy icon (not check icon)
96
+ const checkIcon = copyButton.querySelector(".lucide-check");
97
+ expect(checkIcon).toBeNull();
98
+ const copyIcon = copyButton.querySelector(".lucide-copy");
99
+ expect(copyIcon).not.toBeNull();
100
+ });
101
+
102
+ it("logs error when clipboard write rejects", async () => {
103
+ const writeTextMock = vi
104
+ .fn()
105
+ .mockRejectedValue(new Error("Permission denied"));
106
+ Object.defineProperty(navigator, "clipboard", {
107
+ value: { writeText: writeTextMock },
108
+ writable: true,
109
+ configurable: true,
110
+ });
111
+
112
+ const consoleSpy = vi
113
+ .spyOn(console, "error")
114
+ .mockImplementation(() => {});
115
+
116
+ const message = createAssistantMessage("Hello assistant");
117
+ render(
118
+ <TestWrapper>
119
+ <CopilotChatAssistantMessage message={message} />
120
+ </TestWrapper>,
121
+ );
122
+
123
+ const copyButton = screen.getByTestId("copilot-copy-button");
124
+ fireEvent.click(copyButton);
125
+
126
+ await waitFor(() => {
127
+ expect(writeTextMock).toHaveBeenCalled();
128
+ });
129
+
130
+ await waitFor(() => {
131
+ expect(consoleSpy).toHaveBeenCalledWith(
132
+ "Failed to copy to clipboard:",
133
+ expect.any(Error),
134
+ );
135
+ });
136
+
137
+ // Should NOT show copied state when write failed
138
+ const checkIcon = copyButton.querySelector(".lucide-check");
139
+ expect(checkIcon).toBeNull();
140
+
141
+ consoleSpy.mockRestore();
142
+ });
143
+ });
144
+
145
+ describe("UserMessage CopyButton", () => {
146
+ it("shows copied state only after successful clipboard write", async () => {
147
+ const writeTextMock = vi.fn().mockResolvedValue(undefined);
148
+ Object.defineProperty(navigator, "clipboard", {
149
+ value: { writeText: writeTextMock },
150
+ writable: true,
151
+ configurable: true,
152
+ });
153
+
154
+ const message = createUserMessage("Hello user");
155
+ render(
156
+ <TestWrapper>
157
+ <CopilotChatUserMessage message={message} />
158
+ </TestWrapper>,
159
+ );
160
+
161
+ const copyButton = screen.getByTestId("copilot-user-copy-button");
162
+ fireEvent.click(copyButton);
163
+
164
+ await waitFor(() => {
165
+ expect(writeTextMock).toHaveBeenCalledWith("Hello user");
166
+ });
167
+
168
+ await waitFor(() => {
169
+ const checkIcon = copyButton.querySelector(".lucide-check");
170
+ expect(checkIcon).not.toBeNull();
171
+ });
172
+ });
173
+
174
+ it("does NOT show copied state when clipboard API is unavailable", async () => {
175
+ Object.defineProperty(navigator, "clipboard", {
176
+ value: undefined,
177
+ writable: true,
178
+ configurable: true,
179
+ });
180
+
181
+ const message = createUserMessage("Hello user");
182
+ render(
183
+ <TestWrapper>
184
+ <CopilotChatUserMessage message={message} />
185
+ </TestWrapper>,
186
+ );
187
+
188
+ const copyButton = screen.getByTestId("copilot-user-copy-button");
189
+ fireEvent.click(copyButton);
190
+
191
+ await new Promise((r) => setTimeout(r, 50));
192
+
193
+ const checkIcon = copyButton.querySelector(".lucide-check");
194
+ expect(checkIcon).toBeNull();
195
+ const copyIcon = copyButton.querySelector(".lucide-copy");
196
+ expect(copyIcon).not.toBeNull();
197
+ });
198
+
199
+ it("logs error when clipboard write rejects", async () => {
200
+ const writeTextMock = vi
201
+ .fn()
202
+ .mockRejectedValue(new Error("Permission denied"));
203
+ Object.defineProperty(navigator, "clipboard", {
204
+ value: { writeText: writeTextMock },
205
+ writable: true,
206
+ configurable: true,
207
+ });
208
+
209
+ const consoleSpy = vi
210
+ .spyOn(console, "error")
211
+ .mockImplementation(() => {});
212
+
213
+ const message = createUserMessage("Hello user");
214
+ render(
215
+ <TestWrapper>
216
+ <CopilotChatUserMessage message={message} />
217
+ </TestWrapper>,
218
+ );
219
+
220
+ const copyButton = screen.getByTestId("copilot-user-copy-button");
221
+ fireEvent.click(copyButton);
222
+
223
+ await waitFor(() => {
224
+ expect(writeTextMock).toHaveBeenCalled();
225
+ });
226
+
227
+ await waitFor(() => {
228
+ expect(consoleSpy).toHaveBeenCalledWith(
229
+ "Failed to copy to clipboard:",
230
+ expect.any(Error),
231
+ );
232
+ });
233
+
234
+ // Should NOT show copied state when write failed
235
+ const checkIcon = copyButton.querySelector(".lucide-check");
236
+ expect(checkIcon).toBeNull();
237
+
238
+ consoleSpy.mockRestore();
239
+ });
240
+ });
241
+ });