@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,531 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
-
import { vi } from "vitest";
|
|
4
|
-
import { CopilotChatInput } from "../CopilotChatInput";
|
|
5
|
-
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
6
|
-
|
|
7
|
-
// Mock onSubmitMessage function to track calls
|
|
8
|
-
const mockOnSubmitMessage = vi.fn();
|
|
9
|
-
|
|
10
|
-
// Helper to render components with context provider
|
|
11
|
-
const renderWithProvider = (component: React.ReactElement) => {
|
|
12
|
-
return render(
|
|
13
|
-
<CopilotChatConfigurationProvider>
|
|
14
|
-
{component}
|
|
15
|
-
</CopilotChatConfigurationProvider>
|
|
16
|
-
);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// Clear mocks before each test
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
mockOnSubmitMessage.mockClear();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("CopilotChatInput", () => {
|
|
25
|
-
it("renders with default components and styling", () => {
|
|
26
|
-
const mockOnChange = vi.fn();
|
|
27
|
-
renderWithProvider(
|
|
28
|
-
<CopilotChatInput
|
|
29
|
-
value=""
|
|
30
|
-
onChange={mockOnChange}
|
|
31
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const input = screen.getByPlaceholderText("Type a message...");
|
|
36
|
-
const button = screen.getByRole("button");
|
|
37
|
-
|
|
38
|
-
expect(input).toBeDefined();
|
|
39
|
-
expect(button).toBeDefined();
|
|
40
|
-
expect((button as HTMLButtonElement).disabled).toBe(true); // Should be disabled when input is empty
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("calls onSubmitMessage with trimmed text when Enter is pressed", () => {
|
|
44
|
-
const mockOnChange = vi.fn();
|
|
45
|
-
renderWithProvider(
|
|
46
|
-
<CopilotChatInput
|
|
47
|
-
value=" hello world "
|
|
48
|
-
onChange={mockOnChange}
|
|
49
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const input = screen.getByPlaceholderText("Type a message...");
|
|
54
|
-
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
55
|
-
|
|
56
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("hello world");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("calls onSubmitMessage when button is clicked", () => {
|
|
60
|
-
const mockOnChange = vi.fn();
|
|
61
|
-
renderWithProvider(
|
|
62
|
-
<CopilotChatInput
|
|
63
|
-
value="test message"
|
|
64
|
-
onChange={mockOnChange}
|
|
65
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const button = screen.getByRole("button");
|
|
70
|
-
fireEvent.click(button);
|
|
71
|
-
|
|
72
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("test message");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("does not send when Enter is pressed with Shift key", () => {
|
|
76
|
-
const mockOnChange = vi.fn();
|
|
77
|
-
renderWithProvider(
|
|
78
|
-
<CopilotChatInput
|
|
79
|
-
value="test"
|
|
80
|
-
onChange={mockOnChange}
|
|
81
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
82
|
-
/>
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const input = screen.getByPlaceholderText("Type a message...");
|
|
86
|
-
fireEvent.keyDown(input, { key: "Enter", shiftKey: true });
|
|
87
|
-
|
|
88
|
-
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("does not send empty or whitespace-only messages", () => {
|
|
92
|
-
const mockOnChange = vi.fn();
|
|
93
|
-
|
|
94
|
-
// Test empty string
|
|
95
|
-
const { rerender } = renderWithProvider(
|
|
96
|
-
<CopilotChatInput
|
|
97
|
-
value=""
|
|
98
|
-
onChange={mockOnChange}
|
|
99
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
100
|
-
/>
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const button = screen.getByRole("button");
|
|
104
|
-
fireEvent.click(button);
|
|
105
|
-
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
106
|
-
|
|
107
|
-
// Test whitespace only
|
|
108
|
-
rerender(
|
|
109
|
-
<CopilotChatConfigurationProvider>
|
|
110
|
-
<CopilotChatInput
|
|
111
|
-
value=" "
|
|
112
|
-
onChange={mockOnChange}
|
|
113
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
114
|
-
/>
|
|
115
|
-
</CopilotChatConfigurationProvider>
|
|
116
|
-
);
|
|
117
|
-
fireEvent.click(button);
|
|
118
|
-
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("enables button based on value prop", () => {
|
|
122
|
-
const mockOnChange = vi.fn();
|
|
123
|
-
|
|
124
|
-
// Test with empty value
|
|
125
|
-
const { rerender } = renderWithProvider(
|
|
126
|
-
<CopilotChatInput
|
|
127
|
-
value=""
|
|
128
|
-
onChange={mockOnChange}
|
|
129
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const button = screen.getByRole("button");
|
|
134
|
-
expect((button as HTMLButtonElement).disabled).toBe(true);
|
|
135
|
-
|
|
136
|
-
// Test with non-empty value
|
|
137
|
-
rerender(
|
|
138
|
-
<CopilotChatConfigurationProvider>
|
|
139
|
-
<CopilotChatInput
|
|
140
|
-
value="hello"
|
|
141
|
-
onChange={mockOnChange}
|
|
142
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
143
|
-
/>
|
|
144
|
-
</CopilotChatConfigurationProvider>
|
|
145
|
-
);
|
|
146
|
-
expect((button as HTMLButtonElement).disabled).toBe(false);
|
|
147
|
-
|
|
148
|
-
// Test with empty value again
|
|
149
|
-
rerender(
|
|
150
|
-
<CopilotChatConfigurationProvider>
|
|
151
|
-
<CopilotChatInput
|
|
152
|
-
value=""
|
|
153
|
-
onChange={mockOnChange}
|
|
154
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
155
|
-
/>
|
|
156
|
-
</CopilotChatConfigurationProvider>
|
|
157
|
-
);
|
|
158
|
-
expect((button as HTMLButtonElement).disabled).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("accepts custom slot classes", () => {
|
|
162
|
-
const mockOnChange = vi.fn();
|
|
163
|
-
const { container } = renderWithProvider(
|
|
164
|
-
<CopilotChatInput
|
|
165
|
-
value=""
|
|
166
|
-
onChange={mockOnChange}
|
|
167
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
168
|
-
className="custom-container"
|
|
169
|
-
textArea="custom-textarea"
|
|
170
|
-
sendButton="custom-button"
|
|
171
|
-
/>
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const containerDiv = container.firstChild as HTMLElement;
|
|
175
|
-
const input = screen.getByPlaceholderText("Type a message...");
|
|
176
|
-
const button = screen.getByRole("button");
|
|
177
|
-
|
|
178
|
-
expect(containerDiv.classList.contains("custom-container")).toBe(true);
|
|
179
|
-
expect(input.classList.contains("custom-textarea")).toBe(true);
|
|
180
|
-
expect(button.classList.contains("custom-button")).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("accepts custom components via slots", () => {
|
|
184
|
-
const mockOnChange = vi.fn();
|
|
185
|
-
const CustomButton = (
|
|
186
|
-
props: React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
187
|
-
) => (
|
|
188
|
-
<button {...props} data-testid="custom-button">
|
|
189
|
-
Send Now
|
|
190
|
-
</button>
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
renderWithProvider(
|
|
194
|
-
<CopilotChatInput
|
|
195
|
-
value=""
|
|
196
|
-
onChange={mockOnChange}
|
|
197
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
198
|
-
sendButton={CustomButton}
|
|
199
|
-
/>
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const customButton = screen.getByTestId("custom-button");
|
|
203
|
-
expect(customButton).toBeDefined();
|
|
204
|
-
expect(customButton.textContent?.includes("Send Now")).toBe(true);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("supports custom layout via children render prop", () => {
|
|
208
|
-
const mockOnChange = vi.fn();
|
|
209
|
-
renderWithProvider(
|
|
210
|
-
<CopilotChatInput
|
|
211
|
-
value=""
|
|
212
|
-
onChange={mockOnChange}
|
|
213
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
214
|
-
>
|
|
215
|
-
{({ textArea: TextArea, sendButton: SendButton }) => (
|
|
216
|
-
<div data-testid="custom-layout">
|
|
217
|
-
Custom Layout:
|
|
218
|
-
{SendButton}
|
|
219
|
-
{TextArea}
|
|
220
|
-
</div>
|
|
221
|
-
)}
|
|
222
|
-
</CopilotChatInput>
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const customLayout = screen.getByTestId("custom-layout");
|
|
226
|
-
expect(customLayout).toBeDefined();
|
|
227
|
-
expect(customLayout.textContent?.includes("Custom Layout:")).toBe(true);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("shows cancel and finish buttons in transcribe mode", () => {
|
|
231
|
-
const { container } = renderWithProvider(
|
|
232
|
-
<CopilotChatInput
|
|
233
|
-
mode="transcribe"
|
|
234
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
235
|
-
onStartTranscribe={() => {}}
|
|
236
|
-
onCancelTranscribe={() => {}}
|
|
237
|
-
onFinishTranscribe={() => {}}
|
|
238
|
-
onAddFile={() => {}}
|
|
239
|
-
/>
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Should show cancel button (X icon) - find by svg class
|
|
243
|
-
const cancelIcon = container.querySelector("svg.lucide-x");
|
|
244
|
-
expect(cancelIcon).toBeDefined();
|
|
245
|
-
|
|
246
|
-
// Should show finish button (checkmark icon) - find by svg class
|
|
247
|
-
const finishIcon = container.querySelector("svg.lucide-check");
|
|
248
|
-
expect(finishIcon).toBeDefined();
|
|
249
|
-
|
|
250
|
-
// Should show cancel button (X icon) and finish button (check icon)
|
|
251
|
-
const cancelButton = container.querySelector("svg.lucide-x");
|
|
252
|
-
const finishButton = container.querySelector("svg.lucide-check");
|
|
253
|
-
expect(cancelButton).toBeDefined();
|
|
254
|
-
expect(finishButton).toBeDefined();
|
|
255
|
-
|
|
256
|
-
// Should NOT show transcribe button (mic icon) in transcribe mode
|
|
257
|
-
const transcribeIcon = container.querySelector("svg.lucide-mic");
|
|
258
|
-
expect(transcribeIcon).toBeNull();
|
|
259
|
-
|
|
260
|
-
// Should NOT show send button (arrow-up icon) in transcribe mode
|
|
261
|
-
const sendIcon = container.querySelector("svg.lucide-arrow-up");
|
|
262
|
-
expect(sendIcon).toBeNull();
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("disables add and tools buttons in transcribe mode", () => {
|
|
266
|
-
const { container } = renderWithProvider(
|
|
267
|
-
<CopilotChatInput
|
|
268
|
-
mode="transcribe"
|
|
269
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
270
|
-
onStartTranscribe={() => {}}
|
|
271
|
-
onCancelTranscribe={() => {}}
|
|
272
|
-
onFinishTranscribe={() => {}}
|
|
273
|
-
onAddFile={() => {}}
|
|
274
|
-
toolsMenu={[{ label: "Test Tool", action: () => {} }]}
|
|
275
|
-
/>
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Add button should be disabled (find by Plus icon)
|
|
279
|
-
const addIcon = container.querySelector("svg.lucide-plus");
|
|
280
|
-
const addButton = addIcon?.closest("button");
|
|
281
|
-
expect((addButton as HTMLButtonElement).disabled).toBe(true);
|
|
282
|
-
|
|
283
|
-
// Tools button should be disabled (find by "Tools" text)
|
|
284
|
-
const toolsButton = screen.getByRole("button", { name: /tools/i });
|
|
285
|
-
expect((toolsButton as HTMLButtonElement).disabled).toBe(true);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("shows recording indicator instead of textarea in transcribe mode", () => {
|
|
289
|
-
const { container } = renderWithProvider(
|
|
290
|
-
<CopilotChatInput
|
|
291
|
-
mode="transcribe"
|
|
292
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
293
|
-
onStartTranscribe={() => {}}
|
|
294
|
-
onCancelTranscribe={() => {}}
|
|
295
|
-
onFinishTranscribe={() => {}}
|
|
296
|
-
onAddFile={() => {}}
|
|
297
|
-
/>
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Should show recording indicator (canvas element)
|
|
301
|
-
const recordingIndicator = container.querySelector("canvas");
|
|
302
|
-
expect(recordingIndicator).toBeDefined();
|
|
303
|
-
|
|
304
|
-
// Should NOT show textarea in transcribe mode
|
|
305
|
-
expect(screen.queryByRole("textbox")).toBeNull();
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("shows textarea in input mode", () => {
|
|
309
|
-
const { container } = renderWithProvider(
|
|
310
|
-
<CopilotChatInput
|
|
311
|
-
mode="input"
|
|
312
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
313
|
-
onStartTranscribe={() => {}}
|
|
314
|
-
onCancelTranscribe={() => {}}
|
|
315
|
-
onFinishTranscribe={() => {}}
|
|
316
|
-
onAddFile={() => {}}
|
|
317
|
-
/>
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
// Should show textarea in input mode
|
|
321
|
-
expect(screen.getByRole("textbox")).toBeDefined();
|
|
322
|
-
|
|
323
|
-
// Should NOT show recording indicator (red div)
|
|
324
|
-
const recordingIndicator = container.querySelector(".bg-red-500");
|
|
325
|
-
expect(recordingIndicator).toBeNull();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// Controlled component tests
|
|
329
|
-
describe("Controlled component behavior", () => {
|
|
330
|
-
it("displays the provided value prop", () => {
|
|
331
|
-
const mockOnChange = vi.fn();
|
|
332
|
-
const mockOnSubmitMessage = vi.fn();
|
|
333
|
-
|
|
334
|
-
renderWithProvider(
|
|
335
|
-
<CopilotChatInput
|
|
336
|
-
value="test value"
|
|
337
|
-
onChange={mockOnChange}
|
|
338
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
339
|
-
/>
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
const input = screen.getByRole("textbox");
|
|
343
|
-
expect((input as HTMLTextAreaElement).value).toBe("test value");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("calls onChange when user types", () => {
|
|
347
|
-
const mockOnChange = vi.fn();
|
|
348
|
-
const mockOnSubmitMessage = vi.fn();
|
|
349
|
-
|
|
350
|
-
renderWithProvider(
|
|
351
|
-
<CopilotChatInput
|
|
352
|
-
value=""
|
|
353
|
-
onChange={mockOnChange}
|
|
354
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
355
|
-
/>
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const input = screen.getByRole("textbox");
|
|
359
|
-
fireEvent.change(input, { target: { value: "new text" } });
|
|
360
|
-
|
|
361
|
-
expect(mockOnChange).toHaveBeenCalledWith("new text");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("calls onSubmitMessage when form is submitted", () => {
|
|
365
|
-
const mockOnChange = vi.fn();
|
|
366
|
-
const mockOnSubmitMessage = vi.fn();
|
|
367
|
-
|
|
368
|
-
renderWithProvider(
|
|
369
|
-
<CopilotChatInput
|
|
370
|
-
value="hello world"
|
|
371
|
-
onChange={mockOnChange}
|
|
372
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
373
|
-
/>
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
const input = screen.getByRole("textbox");
|
|
377
|
-
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
378
|
-
|
|
379
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("hello world");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("calls onSubmitMessage when send button is clicked", () => {
|
|
383
|
-
const mockOnChange = vi.fn();
|
|
384
|
-
const mockOnSubmitMessage = vi.fn();
|
|
385
|
-
|
|
386
|
-
renderWithProvider(
|
|
387
|
-
<CopilotChatInput
|
|
388
|
-
value="test message"
|
|
389
|
-
onChange={mockOnChange}
|
|
390
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
391
|
-
/>
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
const button = screen.getByRole("button");
|
|
395
|
-
fireEvent.click(button);
|
|
396
|
-
|
|
397
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("test message");
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it("trims whitespace when submitting", () => {
|
|
401
|
-
const mockOnChange = vi.fn();
|
|
402
|
-
const mockOnSubmitMessage = vi.fn();
|
|
403
|
-
|
|
404
|
-
renderWithProvider(
|
|
405
|
-
<CopilotChatInput
|
|
406
|
-
value=" hello world "
|
|
407
|
-
onChange={mockOnChange}
|
|
408
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
409
|
-
/>
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
const input = screen.getByRole("textbox");
|
|
413
|
-
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
414
|
-
|
|
415
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("hello world");
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it("does not submit empty or whitespace-only messages", () => {
|
|
419
|
-
const mockOnChange = vi.fn();
|
|
420
|
-
const mockOnSubmitMessage = vi.fn();
|
|
421
|
-
|
|
422
|
-
renderWithProvider(
|
|
423
|
-
<CopilotChatInput
|
|
424
|
-
value=" "
|
|
425
|
-
onChange={mockOnChange}
|
|
426
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
427
|
-
/>
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
const button = screen.getByRole("button");
|
|
431
|
-
fireEvent.click(button);
|
|
432
|
-
|
|
433
|
-
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("disables send button when onSubmitMessage is not provided", () => {
|
|
437
|
-
const mockOnChange = vi.fn();
|
|
438
|
-
|
|
439
|
-
renderWithProvider(
|
|
440
|
-
<CopilotChatInput value="some text" onChange={mockOnChange} />
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
const button = screen.getByRole("button");
|
|
444
|
-
expect((button as HTMLButtonElement).disabled).toBe(true);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it("disables send button when value is empty", () => {
|
|
448
|
-
const mockOnChange = vi.fn();
|
|
449
|
-
const mockOnSubmitMessage = vi.fn();
|
|
450
|
-
|
|
451
|
-
renderWithProvider(
|
|
452
|
-
<CopilotChatInput
|
|
453
|
-
value=""
|
|
454
|
-
onChange={mockOnChange}
|
|
455
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
456
|
-
/>
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
const button = screen.getByRole("button");
|
|
460
|
-
expect((button as HTMLButtonElement).disabled).toBe(true);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("enables send button when value has content and onSubmitMessage is provided", () => {
|
|
464
|
-
const mockOnChange = vi.fn();
|
|
465
|
-
const mockOnSubmitMessage = vi.fn();
|
|
466
|
-
|
|
467
|
-
renderWithProvider(
|
|
468
|
-
<CopilotChatInput
|
|
469
|
-
value="hello"
|
|
470
|
-
onChange={mockOnChange}
|
|
471
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
472
|
-
/>
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
const button = screen.getByRole("button");
|
|
476
|
-
expect((button as HTMLButtonElement).disabled).toBe(false);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it("works as a fully controlled component", () => {
|
|
480
|
-
const mockOnChange = vi.fn();
|
|
481
|
-
const mockOnSubmitMessage = vi.fn();
|
|
482
|
-
|
|
483
|
-
const { rerender } = renderWithProvider(
|
|
484
|
-
<CopilotChatInput
|
|
485
|
-
value="initial"
|
|
486
|
-
onChange={mockOnChange}
|
|
487
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
488
|
-
/>
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
const input = screen.getByRole("textbox");
|
|
492
|
-
expect((input as HTMLTextAreaElement).value).toBe("initial");
|
|
493
|
-
|
|
494
|
-
// Simulate parent component updating the value
|
|
495
|
-
rerender(
|
|
496
|
-
<CopilotChatConfigurationProvider>
|
|
497
|
-
<CopilotChatInput
|
|
498
|
-
value="updated"
|
|
499
|
-
onChange={mockOnChange}
|
|
500
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
501
|
-
/>
|
|
502
|
-
</CopilotChatConfigurationProvider>
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
expect((input as HTMLTextAreaElement).value).toBe("updated");
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it("does not clear input after submission when controlled", () => {
|
|
509
|
-
const mockOnChange = vi.fn();
|
|
510
|
-
const mockOnSubmitMessage = vi.fn();
|
|
511
|
-
|
|
512
|
-
renderWithProvider(
|
|
513
|
-
<CopilotChatInput
|
|
514
|
-
value="test message"
|
|
515
|
-
onChange={mockOnChange}
|
|
516
|
-
onSubmitMessage={mockOnSubmitMessage}
|
|
517
|
-
/>
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
const input = screen.getByRole("textbox");
|
|
521
|
-
const button = screen.getByRole("button");
|
|
522
|
-
|
|
523
|
-
fireEvent.click(button);
|
|
524
|
-
|
|
525
|
-
// In controlled mode, the component should not clear the input
|
|
526
|
-
// It's up to the parent to manage the value
|
|
527
|
-
expect((input as HTMLTextAreaElement).value).toBe("test message");
|
|
528
|
-
expect(mockOnSubmitMessage).toHaveBeenCalledWith("test message");
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import "@testing-library/jest-dom";
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
default as CopilotChatInput,
|
|
3
|
-
type CopilotChatInputProps,
|
|
4
|
-
type ToolsMenuItem,
|
|
5
|
-
} from "./CopilotChatInput";
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
default as CopilotChatAssistantMessage,
|
|
9
|
-
type CopilotChatAssistantMessageProps,
|
|
10
|
-
} from "./CopilotChatAssistantMessage";
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
default as CopilotChatUserMessage,
|
|
14
|
-
type CopilotChatUserMessageProps,
|
|
15
|
-
} from "./CopilotChatUserMessage";
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
CopilotChatAudioRecorder,
|
|
19
|
-
type AudioRecorderState,
|
|
20
|
-
AudioRecorderError,
|
|
21
|
-
} from "./CopilotChatAudioRecorder";
|
|
22
|
-
|
|
23
|
-
export {
|
|
24
|
-
default as CopilotChatMessageView,
|
|
25
|
-
type CopilotChatMessageViewProps,
|
|
26
|
-
} from "./CopilotChatMessageView";
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
default as CopilotChatToolCallsView,
|
|
30
|
-
type CopilotChatToolCallsViewProps,
|
|
31
|
-
} from "./CopilotChatToolCallsView";
|
|
32
|
-
|
|
33
|
-
export { default as CopilotChatView, type CopilotChatViewProps } from "./CopilotChatView";
|
|
34
|
-
|
|
35
|
-
export { CopilotChat, type CopilotChatProps } from "./CopilotChat";
|
package/src/components/index.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default:
|
|
13
|
-
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
14
|
-
destructive:
|
|
15
|
-
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
|
-
outline:
|
|
17
|
-
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
18
|
-
secondary:
|
|
19
|
-
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
|
-
ghost:
|
|
21
|
-
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 cursor-pointer",
|
|
22
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
-
assistantMessageToolbarButton: [
|
|
24
|
-
"cursor-pointer",
|
|
25
|
-
// Background and text
|
|
26
|
-
"p-0 text-[rgb(93,93,93)] hover:bg-[#E8E8E8]",
|
|
27
|
-
// Dark mode - lighter gray for better contrast
|
|
28
|
-
"dark:text-[rgb(243,243,243)] dark:hover:bg-[#303030]",
|
|
29
|
-
// Shape and sizing
|
|
30
|
-
"h-8 w-8",
|
|
31
|
-
// Interactions
|
|
32
|
-
"transition-colors",
|
|
33
|
-
// Hover states
|
|
34
|
-
"hover:text-[rgb(93,93,93)]",
|
|
35
|
-
"dark:hover:text-[rgb(243,243,243)]",
|
|
36
|
-
],
|
|
37
|
-
chatInputToolbarPrimary: [
|
|
38
|
-
"cursor-pointer",
|
|
39
|
-
// Background and text
|
|
40
|
-
"bg-black text-white",
|
|
41
|
-
// Dark mode
|
|
42
|
-
"dark:bg-white dark:text-black dark:focus-visible:outline-white",
|
|
43
|
-
// Shape and sizing
|
|
44
|
-
"rounded-full",
|
|
45
|
-
// Interactions
|
|
46
|
-
"transition-colors",
|
|
47
|
-
// Focus states
|
|
48
|
-
"focus:outline-none",
|
|
49
|
-
// Hover states
|
|
50
|
-
"hover:opacity-70 disabled:hover:opacity-100",
|
|
51
|
-
// Disabled states
|
|
52
|
-
"disabled:cursor-not-allowed disabled:bg-[#00000014] disabled:text-[rgb(13,13,13)]",
|
|
53
|
-
"dark:disabled:bg-[#454545] dark:disabled:text-white ",
|
|
54
|
-
],
|
|
55
|
-
chatInputToolbarSecondary: [
|
|
56
|
-
"cursor-pointer",
|
|
57
|
-
// Background and text
|
|
58
|
-
"bg-transparent text-[#444444]",
|
|
59
|
-
// Dark mode
|
|
60
|
-
"dark:text-white dark:border-[#404040]",
|
|
61
|
-
// Shape and sizing
|
|
62
|
-
"rounded-full",
|
|
63
|
-
// Interactions
|
|
64
|
-
"transition-colors",
|
|
65
|
-
// Focus states
|
|
66
|
-
"focus:outline-none",
|
|
67
|
-
// Hover states
|
|
68
|
-
"hover:bg-[#f8f8f8] hover:text-[#333333]",
|
|
69
|
-
"dark:hover:bg-[#404040] dark:hover:text-[#FFFFFF]",
|
|
70
|
-
// Disabled states
|
|
71
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
72
|
-
"disabled:hover:bg-transparent disabled:hover:text-[#444444]",
|
|
73
|
-
"dark:disabled:hover:bg-transparent dark:disabled:hover:text-[#CCCCCC]",
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
size: {
|
|
77
|
-
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
78
|
-
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
79
|
-
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
80
|
-
icon: "size-9",
|
|
81
|
-
chatInputToolbarIcon: [
|
|
82
|
-
// Shape and sizing
|
|
83
|
-
"h-9 w-9 rounded-full",
|
|
84
|
-
],
|
|
85
|
-
chatInputToolbarIconLabel: [
|
|
86
|
-
// Shape and sizing
|
|
87
|
-
"h-9 px-3 rounded-full",
|
|
88
|
-
// Layout
|
|
89
|
-
"gap-2",
|
|
90
|
-
// Typography
|
|
91
|
-
"font-normal",
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
defaultVariants: {
|
|
96
|
-
variant: "default",
|
|
97
|
-
size: "default",
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
function Button({
|
|
103
|
-
className,
|
|
104
|
-
variant,
|
|
105
|
-
size,
|
|
106
|
-
asChild = false,
|
|
107
|
-
...props
|
|
108
|
-
}: React.ComponentProps<"button"> &
|
|
109
|
-
VariantProps<typeof buttonVariants> & {
|
|
110
|
-
asChild?: boolean;
|
|
111
|
-
}) {
|
|
112
|
-
const Comp = asChild ? Slot : "button";
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<Comp
|
|
116
|
-
data-slot="button"
|
|
117
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
118
|
-
{...props}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export { Button, buttonVariants };
|