@copilotkitnext/react 0.0.3 → 0.0.4
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.
- package/package.json +3 -3
- package/.turbo/turbo-build$colon$css.log +0 -9
- package/.turbo/turbo-build.log +0 -30
- package/.turbo/turbo-check-types.log +0 -7
- package/.turbo/turbo-lint.log +0 -78
- package/.turbo/turbo-test.log +0 -79
- package/postcss.config.js +0 -7
- package/src/__tests__/setup.ts +0 -2
- package/src/components/chat/CopilotChat.tsx +0 -90
- package/src/components/chat/CopilotChatAssistantMessage.tsx +0 -478
- package/src/components/chat/CopilotChatAudioRecorder.tsx +0 -157
- package/src/components/chat/CopilotChatInput.tsx +0 -596
- package/src/components/chat/CopilotChatMessageView.tsx +0 -85
- package/src/components/chat/CopilotChatToolCallsView.tsx +0 -43
- package/src/components/chat/CopilotChatUserMessage.tsx +0 -337
- package/src/components/chat/CopilotChatView.tsx +0 -385
- package/src/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +0 -684
- package/src/components/chat/__tests__/CopilotChatInput.test.tsx +0 -531
- package/src/components/chat/__tests__/setup.ts +0 -1
- package/src/components/chat/index.ts +0 -35
- package/src/components/index.ts +0 -4
- package/src/components/ui/button.tsx +0 -123
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/tooltip.tsx +0 -59
- package/src/hooks/index.ts +0 -6
- package/src/hooks/use-agent-context.tsx +0 -17
- package/src/hooks/use-agent.tsx +0 -48
- package/src/hooks/use-frontend-tool.tsx +0 -46
- package/src/hooks/use-human-in-the-loop.tsx +0 -76
- package/src/hooks/use-render-tool-call.tsx +0 -81
- package/src/index.ts +0 -4
- package/src/lib/__tests__/completePartialMarkdown.test.ts +0 -495
- package/src/lib/__tests__/renderSlot.test.tsx +0 -610
- package/src/lib/slots.tsx +0 -55
- package/src/lib/utils.ts +0 -6
- package/src/providers/CopilotChatConfigurationProvider.tsx +0 -81
- package/src/providers/CopilotKitProvider.tsx +0 -269
- package/src/providers/__tests__/CopilotKitProvider.test.tsx +0 -487
- package/src/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +0 -261
- package/src/providers/index.ts +0 -14
- package/src/styles/globals.css +0 -302
- package/src/types/frontend-tool.ts +0 -8
- package/src/types/human-in-the-loop.ts +0 -33
- package/src/types/index.ts +0 -3
- package/src/types/react-tool-call-render.ts +0 -29
- package/tailwind.config.js +0 -9
- package/tsconfig.json +0 -23
- package/tsup.config.ts +0 -19
|
@@ -1,684 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
-
import { vi } from "vitest";
|
|
4
|
-
import { CopilotChatAssistantMessage } from "../CopilotChatAssistantMessage";
|
|
5
|
-
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
6
|
-
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
7
|
-
import { AssistantMessage } from "@ag-ui/core";
|
|
8
|
-
|
|
9
|
-
// No mocks needed - Vitest handles ES modules natively!
|
|
10
|
-
|
|
11
|
-
// Mock navigator.clipboard
|
|
12
|
-
const mockWriteText = vi.fn();
|
|
13
|
-
Object.assign(navigator, {
|
|
14
|
-
clipboard: {
|
|
15
|
-
writeText: mockWriteText,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Mock callback functions
|
|
20
|
-
const mockOnThumbsUp = vi.fn();
|
|
21
|
-
const mockOnThumbsDown = vi.fn();
|
|
22
|
-
const mockOnReadAloud = vi.fn();
|
|
23
|
-
const mockOnRegenerate = vi.fn();
|
|
24
|
-
|
|
25
|
-
// Helper to render components with context providers
|
|
26
|
-
const renderWithProvider = (component: React.ReactElement) => {
|
|
27
|
-
return render(
|
|
28
|
-
<CopilotKitProvider publicApiKey="test">
|
|
29
|
-
<CopilotChatConfigurationProvider>
|
|
30
|
-
{component}
|
|
31
|
-
</CopilotChatConfigurationProvider>
|
|
32
|
-
</CopilotKitProvider>
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Clear mocks before each test
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
mockWriteText.mockClear();
|
|
39
|
-
mockOnThumbsUp.mockClear();
|
|
40
|
-
mockOnThumbsDown.mockClear();
|
|
41
|
-
mockOnReadAloud.mockClear();
|
|
42
|
-
mockOnRegenerate.mockClear();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe("CopilotChatAssistantMessage", () => {
|
|
46
|
-
const basicMessage: AssistantMessage = {
|
|
47
|
-
role: "assistant",
|
|
48
|
-
content: "Hello, this is a test message from the assistant.",
|
|
49
|
-
id: "test-message-1",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
describe("Basic rendering", () => {
|
|
53
|
-
it("renders with default components and styling", () => {
|
|
54
|
-
renderWithProvider(
|
|
55
|
-
<CopilotChatAssistantMessage message={basicMessage} />
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Check if elements exist (getBy throws if not found, so this is sufficient)
|
|
59
|
-
// Note: Since markdown may not render in test environment, let's check the component structure
|
|
60
|
-
const copyButton = screen.getByRole("button", { name: /copy/i });
|
|
61
|
-
expect(copyButton).toBeDefined();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("renders empty message gracefully", () => {
|
|
65
|
-
const emptyMessage: AssistantMessage = {
|
|
66
|
-
role: "assistant",
|
|
67
|
-
content: "",
|
|
68
|
-
id: "empty-message",
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
renderWithProvider(
|
|
72
|
-
<CopilotChatAssistantMessage message={emptyMessage} />
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// Should still render the component structure
|
|
76
|
-
screen.getByRole("button", { name: /copy/i });
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe("Callback functionality", () => {
|
|
81
|
-
it("renders only copy button when no callbacks provided", () => {
|
|
82
|
-
renderWithProvider(
|
|
83
|
-
<CopilotChatAssistantMessage message={basicMessage} />
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
expect(screen.getByRole("button", { name: /copy/i })).toBeDefined();
|
|
87
|
-
expect(screen.queryByRole("button", { name: /thumbs up/i })).toBeNull();
|
|
88
|
-
expect(screen.queryByRole("button", { name: /thumbs down/i })).toBeNull();
|
|
89
|
-
expect(screen.queryByRole("button", { name: /read aloud/i })).toBeNull();
|
|
90
|
-
expect(screen.queryByRole("button", { name: /regenerate/i })).toBeNull();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("renders all buttons when all callbacks provided", () => {
|
|
94
|
-
renderWithProvider(
|
|
95
|
-
<CopilotChatAssistantMessage
|
|
96
|
-
message={basicMessage}
|
|
97
|
-
onThumbsUp={mockOnThumbsUp}
|
|
98
|
-
onThumbsDown={mockOnThumbsDown}
|
|
99
|
-
onReadAloud={mockOnReadAloud}
|
|
100
|
-
onRegenerate={mockOnRegenerate}
|
|
101
|
-
/>
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
expect(screen.getByRole("button", { name: /copy/i })).toBeDefined();
|
|
105
|
-
expect(
|
|
106
|
-
screen.getByRole("button", { name: /good response/i })
|
|
107
|
-
).toBeDefined();
|
|
108
|
-
expect(
|
|
109
|
-
screen.getByRole("button", { name: /bad response/i })
|
|
110
|
-
).toBeDefined();
|
|
111
|
-
expect(screen.getByRole("button", { name: /read aloud/i })).toBeDefined();
|
|
112
|
-
expect(screen.getByRole("button", { name: /regenerate/i })).toBeDefined();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("calls copy functionality when copy button clicked", async () => {
|
|
116
|
-
renderWithProvider(
|
|
117
|
-
<CopilotChatAssistantMessage message={basicMessage} />
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const copyButton = screen.getByRole("button", { name: /copy/i });
|
|
121
|
-
fireEvent.click(copyButton);
|
|
122
|
-
|
|
123
|
-
await waitFor(() => {
|
|
124
|
-
expect(mockWriteText).toHaveBeenCalledWith(basicMessage.content!);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("calls thumbs up callback when thumbs up button clicked", () => {
|
|
129
|
-
renderWithProvider(
|
|
130
|
-
<CopilotChatAssistantMessage
|
|
131
|
-
message={basicMessage}
|
|
132
|
-
onThumbsUp={mockOnThumbsUp}
|
|
133
|
-
/>
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const thumbsUpButton = screen.getByRole("button", {
|
|
137
|
-
name: /good response/i,
|
|
138
|
-
});
|
|
139
|
-
fireEvent.click(thumbsUpButton);
|
|
140
|
-
|
|
141
|
-
expect(mockOnThumbsUp).toHaveBeenCalledTimes(1);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("calls thumbs down callback when thumbs down button clicked", () => {
|
|
145
|
-
renderWithProvider(
|
|
146
|
-
<CopilotChatAssistantMessage
|
|
147
|
-
message={basicMessage}
|
|
148
|
-
onThumbsDown={mockOnThumbsDown}
|
|
149
|
-
/>
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const thumbsDownButton = screen.getByRole("button", {
|
|
153
|
-
name: /bad response/i,
|
|
154
|
-
});
|
|
155
|
-
fireEvent.click(thumbsDownButton);
|
|
156
|
-
|
|
157
|
-
expect(mockOnThumbsDown).toHaveBeenCalledTimes(1);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("calls read aloud callback when read aloud button clicked", () => {
|
|
161
|
-
renderWithProvider(
|
|
162
|
-
<CopilotChatAssistantMessage
|
|
163
|
-
message={basicMessage}
|
|
164
|
-
onReadAloud={mockOnReadAloud}
|
|
165
|
-
/>
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const readAloudButton = screen.getByRole("button", {
|
|
169
|
-
name: /read aloud/i,
|
|
170
|
-
});
|
|
171
|
-
fireEvent.click(readAloudButton);
|
|
172
|
-
|
|
173
|
-
expect(mockOnReadAloud).toHaveBeenCalledTimes(1);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("calls regenerate callback when regenerate button clicked", () => {
|
|
177
|
-
renderWithProvider(
|
|
178
|
-
<CopilotChatAssistantMessage
|
|
179
|
-
message={basicMessage}
|
|
180
|
-
onRegenerate={mockOnRegenerate}
|
|
181
|
-
/>
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
const regenerateButton = screen.getByRole("button", {
|
|
185
|
-
name: /regenerate/i,
|
|
186
|
-
});
|
|
187
|
-
fireEvent.click(regenerateButton);
|
|
188
|
-
|
|
189
|
-
expect(mockOnRegenerate).toHaveBeenCalledTimes(1);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("Additional toolbar items", () => {
|
|
194
|
-
it("renders additional toolbar items", () => {
|
|
195
|
-
const additionalItems = (
|
|
196
|
-
<button data-testid="custom-toolbar-item">Custom Action</button>
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
renderWithProvider(
|
|
200
|
-
<CopilotChatAssistantMessage
|
|
201
|
-
message={basicMessage}
|
|
202
|
-
additionalToolbarItems={additionalItems}
|
|
203
|
-
/>
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
expect(screen.getByTestId("custom-toolbar-item")).toBeDefined();
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe("Slot functionality - Custom Components", () => {
|
|
211
|
-
it("accepts custom MarkdownRenderer component", () => {
|
|
212
|
-
const CustomMarkdownRenderer = ({ content }: { content: string }) => (
|
|
213
|
-
<div data-testid="custom-markdown">{content.toUpperCase()}</div>
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
renderWithProvider(
|
|
217
|
-
<CopilotChatAssistantMessage
|
|
218
|
-
message={basicMessage}
|
|
219
|
-
markdownRenderer={CustomMarkdownRenderer}
|
|
220
|
-
/>
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
expect(screen.getByTestId("custom-markdown")).toBeDefined();
|
|
224
|
-
expect(
|
|
225
|
-
screen
|
|
226
|
-
.getByTestId("custom-markdown")
|
|
227
|
-
.textContent?.includes(basicMessage.content!.toUpperCase())
|
|
228
|
-
).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("accepts custom Toolbar component", () => {
|
|
232
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
-
const CustomToolbar = ({ children, ...props }: any) => (
|
|
234
|
-
<div data-testid="custom-toolbar" {...props}>
|
|
235
|
-
Custom Toolbar: {children}
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
renderWithProvider(
|
|
240
|
-
<CopilotChatAssistantMessage
|
|
241
|
-
message={basicMessage}
|
|
242
|
-
toolbar={CustomToolbar}
|
|
243
|
-
/>
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
expect(screen.getByTestId("custom-toolbar")).toBeDefined();
|
|
247
|
-
expect(
|
|
248
|
-
screen
|
|
249
|
-
.getByTestId("custom-toolbar")
|
|
250
|
-
.textContent?.includes("Custom Toolbar:")
|
|
251
|
-
).toBe(true);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("accepts custom CopyButton component", () => {
|
|
255
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
256
|
-
const CustomCopyButton = (props: any) => (
|
|
257
|
-
<button data-testid="custom-copy-button" {...props}>
|
|
258
|
-
Custom Copy
|
|
259
|
-
</button>
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
renderWithProvider(
|
|
263
|
-
<CopilotChatAssistantMessage
|
|
264
|
-
message={basicMessage}
|
|
265
|
-
copyButton={CustomCopyButton}
|
|
266
|
-
/>
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
expect(screen.getByTestId("custom-copy-button")).toBeDefined();
|
|
270
|
-
expect(
|
|
271
|
-
screen
|
|
272
|
-
.getByTestId("custom-copy-button")
|
|
273
|
-
.textContent?.includes("Custom Copy")
|
|
274
|
-
).toBe(true);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("accepts custom ThumbsUpButton component", () => {
|
|
278
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
279
|
-
const CustomThumbsUpButton = (props: any) => (
|
|
280
|
-
<button data-testid="custom-thumbs-up" {...props}>
|
|
281
|
-
Custom Like
|
|
282
|
-
</button>
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
renderWithProvider(
|
|
286
|
-
<CopilotChatAssistantMessage
|
|
287
|
-
message={basicMessage}
|
|
288
|
-
onThumbsUp={mockOnThumbsUp}
|
|
289
|
-
thumbsUpButton={CustomThumbsUpButton}
|
|
290
|
-
/>
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
expect(screen.getByTestId("custom-thumbs-up")).toBeDefined();
|
|
294
|
-
expect(
|
|
295
|
-
screen
|
|
296
|
-
.getByTestId("custom-thumbs-up")
|
|
297
|
-
.textContent?.includes("Custom Like")
|
|
298
|
-
).toBe(true);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it("accepts custom ThumbsDownButton component", () => {
|
|
302
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
303
|
-
const CustomThumbsDownButton = (props: any) => (
|
|
304
|
-
<button data-testid="custom-thumbs-down" {...props}>
|
|
305
|
-
Custom Dislike
|
|
306
|
-
</button>
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
renderWithProvider(
|
|
310
|
-
<CopilotChatAssistantMessage
|
|
311
|
-
message={basicMessage}
|
|
312
|
-
onThumbsDown={mockOnThumbsDown}
|
|
313
|
-
thumbsDownButton={CustomThumbsDownButton}
|
|
314
|
-
/>
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
expect(screen.getByTestId("custom-thumbs-down")).toBeDefined();
|
|
318
|
-
expect(
|
|
319
|
-
screen
|
|
320
|
-
.getByTestId("custom-thumbs-down")
|
|
321
|
-
.textContent?.includes("Custom Dislike")
|
|
322
|
-
).toBe(true);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it("accepts custom ReadAloudButton component", () => {
|
|
326
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
-
const CustomReadAloudButton = (props: any) => (
|
|
328
|
-
<button data-testid="custom-read-aloud" {...props}>
|
|
329
|
-
Custom Speak
|
|
330
|
-
</button>
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
renderWithProvider(
|
|
334
|
-
<CopilotChatAssistantMessage
|
|
335
|
-
message={basicMessage}
|
|
336
|
-
onReadAloud={mockOnReadAloud}
|
|
337
|
-
readAloudButton={CustomReadAloudButton}
|
|
338
|
-
/>
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
expect(screen.getByTestId("custom-read-aloud")).toBeDefined();
|
|
342
|
-
expect(
|
|
343
|
-
screen
|
|
344
|
-
.getByTestId("custom-read-aloud")
|
|
345
|
-
.textContent?.includes("Custom Speak")
|
|
346
|
-
).toBe(true);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("accepts custom RegenerateButton component", () => {
|
|
350
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
351
|
-
const CustomRegenerateButton = (props: any) => (
|
|
352
|
-
<button data-testid="custom-regenerate" {...props}>
|
|
353
|
-
Custom Retry
|
|
354
|
-
</button>
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
renderWithProvider(
|
|
358
|
-
<CopilotChatAssistantMessage
|
|
359
|
-
message={basicMessage}
|
|
360
|
-
onRegenerate={mockOnRegenerate}
|
|
361
|
-
regenerateButton={CustomRegenerateButton}
|
|
362
|
-
/>
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
expect(screen.getByTestId("custom-regenerate")).toBeDefined();
|
|
366
|
-
expect(
|
|
367
|
-
screen
|
|
368
|
-
.getByTestId("custom-regenerate")
|
|
369
|
-
.textContent?.includes("Custom Retry")
|
|
370
|
-
).toBe(true);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe("Slot functionality - Custom Classes", () => {
|
|
375
|
-
it("applies custom className to component", () => {
|
|
376
|
-
const { container } = renderWithProvider(
|
|
377
|
-
<CopilotChatAssistantMessage
|
|
378
|
-
message={basicMessage}
|
|
379
|
-
className="custom-container-class"
|
|
380
|
-
/>
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
const containerElement = container.querySelector(
|
|
384
|
-
".custom-container-class"
|
|
385
|
-
);
|
|
386
|
-
expect(containerElement).toBeDefined();
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("applies custom className to MarkdownRenderer slot", () => {
|
|
390
|
-
const { container } = renderWithProvider(
|
|
391
|
-
<CopilotChatAssistantMessage
|
|
392
|
-
message={basicMessage}
|
|
393
|
-
markdownRenderer="custom-markdown-class"
|
|
394
|
-
/>
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
const markdownElement = container.querySelector(".custom-markdown-class");
|
|
398
|
-
expect(markdownElement).toBeDefined();
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("applies custom className to Toolbar slot", () => {
|
|
402
|
-
const { container } = renderWithProvider(
|
|
403
|
-
<CopilotChatAssistantMessage
|
|
404
|
-
message={basicMessage}
|
|
405
|
-
toolbar="custom-toolbar-class"
|
|
406
|
-
/>
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
const toolbarElement = container.querySelector(".custom-toolbar-class");
|
|
410
|
-
expect(toolbarElement).toBeDefined();
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it("applies custom className to CopyButton slot", () => {
|
|
414
|
-
const { container } = renderWithProvider(
|
|
415
|
-
<CopilotChatAssistantMessage
|
|
416
|
-
message={basicMessage}
|
|
417
|
-
copyButton="custom-copy-button-class"
|
|
418
|
-
/>
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
const copyButtonElement = container.querySelector(
|
|
422
|
-
".custom-copy-button-class"
|
|
423
|
-
);
|
|
424
|
-
expect(copyButtonElement).toBeDefined();
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
describe("Children render prop functionality", () => {
|
|
429
|
-
it("supports custom layout via children render prop", () => {
|
|
430
|
-
renderWithProvider(
|
|
431
|
-
<CopilotChatAssistantMessage message={basicMessage}>
|
|
432
|
-
{({
|
|
433
|
-
markdownRenderer: MarkdownRenderer,
|
|
434
|
-
toolbar: Toolbar,
|
|
435
|
-
message,
|
|
436
|
-
}) => (
|
|
437
|
-
<div data-testid="custom-layout">
|
|
438
|
-
<h2>Custom Layout for: {message.id}</h2>
|
|
439
|
-
{MarkdownRenderer}
|
|
440
|
-
<div data-testid="custom-toolbar-wrapper">{Toolbar}</div>
|
|
441
|
-
</div>
|
|
442
|
-
)}
|
|
443
|
-
</CopilotChatAssistantMessage>
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
expect(screen.getByTestId("custom-layout")).toBeDefined();
|
|
447
|
-
expect(
|
|
448
|
-
screen.getByText(`Custom Layout for: ${basicMessage.id}`)
|
|
449
|
-
).toBeDefined();
|
|
450
|
-
expect(screen.getByTestId("custom-toolbar-wrapper")).toBeDefined();
|
|
451
|
-
// Note: Markdown content may not render in test environment, check toolbar instead
|
|
452
|
-
expect(screen.getByTestId("custom-toolbar-wrapper")).toBeDefined();
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it("provides all slot components to children render prop", () => {
|
|
456
|
-
renderWithProvider(
|
|
457
|
-
<CopilotChatAssistantMessage
|
|
458
|
-
message={basicMessage}
|
|
459
|
-
onThumbsUp={mockOnThumbsUp}
|
|
460
|
-
onThumbsDown={mockOnThumbsDown}
|
|
461
|
-
onReadAloud={mockOnReadAloud}
|
|
462
|
-
onRegenerate={mockOnRegenerate}
|
|
463
|
-
>
|
|
464
|
-
{({
|
|
465
|
-
markdownRenderer: MarkdownRenderer,
|
|
466
|
-
toolbar: Toolbar,
|
|
467
|
-
copyButton: CopyButton,
|
|
468
|
-
thumbsUpButton: ThumbsUpButton,
|
|
469
|
-
thumbsDownButton: ThumbsDownButton,
|
|
470
|
-
readAloudButton: ReadAloudButton,
|
|
471
|
-
regenerateButton: RegenerateButton,
|
|
472
|
-
}) => (
|
|
473
|
-
<div data-testid="all-slots-layout">
|
|
474
|
-
{MarkdownRenderer}
|
|
475
|
-
{Toolbar}
|
|
476
|
-
<div data-testid="individual-buttons">
|
|
477
|
-
{CopyButton}
|
|
478
|
-
{ThumbsUpButton}
|
|
479
|
-
{ThumbsDownButton}
|
|
480
|
-
{ReadAloudButton}
|
|
481
|
-
{RegenerateButton}
|
|
482
|
-
</div>
|
|
483
|
-
</div>
|
|
484
|
-
)}
|
|
485
|
-
</CopilotChatAssistantMessage>
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
expect(screen.getByTestId("all-slots-layout")).toBeDefined();
|
|
489
|
-
expect(screen.getByTestId("individual-buttons")).toBeDefined();
|
|
490
|
-
|
|
491
|
-
// Verify all buttons are rendered
|
|
492
|
-
const buttons = screen.getAllByRole("button");
|
|
493
|
-
expect(buttons.length).toBeGreaterThanOrEqual(5); // At least copy, thumbs up, thumbs down, read aloud, regenerate
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it("provides callback props to children render prop", () => {
|
|
497
|
-
renderWithProvider(
|
|
498
|
-
<CopilotChatAssistantMessage
|
|
499
|
-
message={basicMessage}
|
|
500
|
-
onThumbsUp={mockOnThumbsUp}
|
|
501
|
-
onThumbsDown={mockOnThumbsDown}
|
|
502
|
-
onReadAloud={mockOnReadAloud}
|
|
503
|
-
onRegenerate={mockOnRegenerate}
|
|
504
|
-
>
|
|
505
|
-
{({ onThumbsUp, onThumbsDown, onReadAloud, onRegenerate }) => (
|
|
506
|
-
<div data-testid="callback-test">
|
|
507
|
-
<button
|
|
508
|
-
onClick={() => onThumbsUp?.(basicMessage)}
|
|
509
|
-
data-testid="custom-thumbs-up"
|
|
510
|
-
>
|
|
511
|
-
Custom Thumbs Up
|
|
512
|
-
</button>
|
|
513
|
-
<button
|
|
514
|
-
onClick={() => onThumbsDown?.(basicMessage)}
|
|
515
|
-
data-testid="custom-thumbs-down"
|
|
516
|
-
>
|
|
517
|
-
Custom Thumbs Down
|
|
518
|
-
</button>
|
|
519
|
-
<button
|
|
520
|
-
onClick={() => onReadAloud?.(basicMessage)}
|
|
521
|
-
data-testid="custom-read-aloud"
|
|
522
|
-
>
|
|
523
|
-
Custom Read Aloud
|
|
524
|
-
</button>
|
|
525
|
-
<button
|
|
526
|
-
onClick={() => onRegenerate?.(basicMessage)}
|
|
527
|
-
data-testid="custom-regenerate"
|
|
528
|
-
>
|
|
529
|
-
Custom Regenerate
|
|
530
|
-
</button>
|
|
531
|
-
</div>
|
|
532
|
-
)}
|
|
533
|
-
</CopilotChatAssistantMessage>
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
fireEvent.click(screen.getByTestId("custom-thumbs-up"));
|
|
537
|
-
fireEvent.click(screen.getByTestId("custom-thumbs-down"));
|
|
538
|
-
fireEvent.click(screen.getByTestId("custom-read-aloud"));
|
|
539
|
-
fireEvent.click(screen.getByTestId("custom-regenerate"));
|
|
540
|
-
|
|
541
|
-
expect(mockOnThumbsUp).toHaveBeenCalledTimes(1);
|
|
542
|
-
expect(mockOnThumbsDown).toHaveBeenCalledTimes(1);
|
|
543
|
-
expect(mockOnReadAloud).toHaveBeenCalledTimes(1);
|
|
544
|
-
expect(mockOnRegenerate).toHaveBeenCalledTimes(1);
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
describe("Toolbar visibility functionality", () => {
|
|
549
|
-
it("shows toolbar by default (toolbarVisible = true by default)", () => {
|
|
550
|
-
renderWithProvider(
|
|
551
|
-
<CopilotChatAssistantMessage message={basicMessage} />
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
expect(screen.getByRole("button", { name: /copy/i })).toBeDefined();
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it("shows toolbar when toolbarVisible is explicitly true", () => {
|
|
558
|
-
renderWithProvider(
|
|
559
|
-
<CopilotChatAssistantMessage
|
|
560
|
-
message={basicMessage}
|
|
561
|
-
toolbarVisible={true}
|
|
562
|
-
/>
|
|
563
|
-
);
|
|
564
|
-
|
|
565
|
-
expect(screen.getByRole("button", { name: /copy/i })).toBeDefined();
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it("hides toolbar when toolbarVisible is false", () => {
|
|
569
|
-
renderWithProvider(
|
|
570
|
-
<CopilotChatAssistantMessage
|
|
571
|
-
message={basicMessage}
|
|
572
|
-
toolbarVisible={false}
|
|
573
|
-
/>
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
expect(screen.queryByRole("button", { name: /copy/i })).toBeNull();
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it("always passes toolbar and toolbarVisible to children render prop", () => {
|
|
580
|
-
const childrenSpy = vi.fn(() => <div data-testid="children-render" />);
|
|
581
|
-
|
|
582
|
-
renderWithProvider(
|
|
583
|
-
<CopilotChatAssistantMessage
|
|
584
|
-
message={basicMessage}
|
|
585
|
-
toolbarVisible={false}
|
|
586
|
-
>
|
|
587
|
-
{childrenSpy}
|
|
588
|
-
</CopilotChatAssistantMessage>
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
expect(childrenSpy).toHaveBeenCalledWith(
|
|
592
|
-
expect.objectContaining({
|
|
593
|
-
toolbar: expect.anything(),
|
|
594
|
-
toolbarVisible: false,
|
|
595
|
-
message: basicMessage,
|
|
596
|
-
})
|
|
597
|
-
);
|
|
598
|
-
expect(screen.getByTestId("children-render")).toBeDefined();
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
it("passes toolbarVisible true to children render prop by default", () => {
|
|
602
|
-
const childrenSpy = vi.fn(() => <div data-testid="children-render" />);
|
|
603
|
-
|
|
604
|
-
renderWithProvider(
|
|
605
|
-
<CopilotChatAssistantMessage message={basicMessage}>
|
|
606
|
-
{childrenSpy}
|
|
607
|
-
</CopilotChatAssistantMessage>
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
expect(childrenSpy).toHaveBeenCalledWith(
|
|
611
|
-
expect.objectContaining({
|
|
612
|
-
toolbar: expect.anything(),
|
|
613
|
-
toolbarVisible: true,
|
|
614
|
-
message: basicMessage,
|
|
615
|
-
})
|
|
616
|
-
);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it("children can use toolbarVisible to conditionally render toolbar", () => {
|
|
620
|
-
renderWithProvider(
|
|
621
|
-
<CopilotChatAssistantMessage
|
|
622
|
-
message={basicMessage}
|
|
623
|
-
toolbarVisible={false}
|
|
624
|
-
>
|
|
625
|
-
{({ toolbar, toolbarVisible }) => (
|
|
626
|
-
<div data-testid="custom-layout">
|
|
627
|
-
<div data-testid="content">Custom content</div>
|
|
628
|
-
{toolbarVisible && <div data-testid="conditional-toolbar">{toolbar}</div>}
|
|
629
|
-
{!toolbarVisible && <div data-testid="no-toolbar">No toolbar</div>}
|
|
630
|
-
</div>
|
|
631
|
-
)}
|
|
632
|
-
</CopilotChatAssistantMessage>
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
expect(screen.getByTestId("custom-layout")).toBeDefined();
|
|
636
|
-
expect(screen.getByTestId("content")).toBeDefined();
|
|
637
|
-
expect(screen.queryByTestId("conditional-toolbar")).toBeNull();
|
|
638
|
-
expect(screen.getByTestId("no-toolbar")).toBeDefined();
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
describe("Error handling", () => {
|
|
643
|
-
it("handles copy errors gracefully", async () => {
|
|
644
|
-
// Mock clipboard to throw an error
|
|
645
|
-
mockWriteText.mockRejectedValueOnce(new Error("Clipboard error"));
|
|
646
|
-
|
|
647
|
-
const consoleSpy = vi
|
|
648
|
-
.spyOn(console, "error")
|
|
649
|
-
.mockImplementation(() => {});
|
|
650
|
-
|
|
651
|
-
renderWithProvider(
|
|
652
|
-
<CopilotChatAssistantMessage message={basicMessage} />
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
const copyButton = screen.getByRole("button", { name: /copy/i });
|
|
656
|
-
fireEvent.click(copyButton);
|
|
657
|
-
|
|
658
|
-
await waitFor(() => {
|
|
659
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
660
|
-
"Failed to copy message:",
|
|
661
|
-
expect.any(Error)
|
|
662
|
-
);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
consoleSpy.mockRestore();
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
it("handles null message content gracefully", () => {
|
|
669
|
-
const nullContentMessage: AssistantMessage = {
|
|
670
|
-
role: "assistant",
|
|
671
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
672
|
-
content: null as any,
|
|
673
|
-
id: "null-content",
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
renderWithProvider(
|
|
677
|
-
<CopilotChatAssistantMessage message={nullContentMessage} />
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
// Should still render the component structure
|
|
681
|
-
expect(screen.getByRole("button", { name: /copy/i })).toBeDefined();
|
|
682
|
-
});
|
|
683
|
-
});
|
|
684
|
-
});
|