@copilotkit/react-core 1.54.1 → 1.55.0-next.8
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/CHANGELOG.md +127 -116
- package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
- package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
- package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
- package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
- package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
- package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
- package/dist/index.cjs +27 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -5
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1941 -35
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +77 -7
- package/dist/v2/index.css +1 -2
- package/dist/v2/index.d.cts +6 -4
- package/dist/v2/index.d.mts +6 -4
- package/dist/v2/index.mjs +7 -4
- package/dist/v2/index.umd.js +5725 -24
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +37 -9
- package/scripts/scope-preflight.mjs +101 -0
- package/src/components/CopilotListeners.tsx +2 -6
- package/src/components/copilot-provider/copilot-messages.tsx +1 -1
- package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
- package/src/components/copilot-provider/copilotkit.tsx +4 -4
- package/src/context/copilot-messages-context.tsx +1 -1
- package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
- package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
- package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
- package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
- package/src/hooks/use-agent-nodename.ts +1 -1
- package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
- package/src/hooks/use-coagent.ts +1 -1
- package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat_internal.ts +2 -2
- package/src/hooks/use-copilot-readable.ts +1 -1
- package/src/hooks/use-frontend-tool.ts +2 -2
- package/src/hooks/use-human-in-the-loop.ts +2 -2
- package/src/hooks/use-langgraph-interrupt.ts +2 -5
- package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
- package/src/hooks/use-render-tool-call.ts +1 -1
- package/src/lib/copilot-task.ts +1 -1
- package/src/setupTests.ts +18 -14
- package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
- package/src/v2/__tests__/globalSetup.ts +14 -0
- package/src/v2/__tests__/setup.ts +93 -0
- package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
- package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
- package/src/v2/components/CopilotKitInspector.tsx +50 -0
- package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
- package/src/v2/components/WildcardToolCallRender.tsx +86 -0
- package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
- package/src/v2/components/chat/CopilotChat.tsx +431 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
- package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
- package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
- package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
- package/src/v2/components/chat/CopilotChatView.tsx +598 -0
- package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
- package/src/v2/components/chat/CopilotPopup.tsx +81 -0
- package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
- package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
- package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
- package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
- package/src/v2/components/chat/__tests__/setup.ts +1 -0
- package/src/v2/components/chat/index.ts +79 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/components/license-warning-banner.tsx +198 -0
- package/src/v2/components/ui/button.tsx +123 -0
- package/src/v2/components/ui/dropdown-menu.tsx +258 -0
- package/src/v2/components/ui/tooltip.tsx +60 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
- package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
- package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
- package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
- package/src/v2/hooks/index.ts +18 -0
- package/src/v2/hooks/use-agent-context.tsx +45 -0
- package/src/v2/hooks/use-agent.tsx +155 -0
- package/src/v2/hooks/use-component.tsx +89 -0
- package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
- package/src/v2/hooks/use-default-render-tool.tsx +254 -0
- package/src/v2/hooks/use-frontend-tool.tsx +43 -0
- package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
- package/src/v2/hooks/use-interrupt.tsx +305 -0
- package/src/v2/hooks/use-keyboard-height.tsx +67 -0
- package/src/v2/hooks/use-render-activity-message.tsx +73 -0
- package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
- package/src/v2/hooks/use-render-tool-call.tsx +175 -0
- package/src/v2/hooks/use-render-tool.tsx +181 -0
- package/src/v2/hooks/use-suggestions.tsx +91 -0
- package/src/v2/hooks/use-threads.tsx +256 -0
- package/src/v2/hooks/useKatexStyles.ts +27 -0
- package/src/v2/index.css +1 -1
- package/src/v2/index.ts +18 -2
- package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
- package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
- package/src/v2/lib/react-core.ts +156 -0
- package/src/v2/lib/slots.tsx +143 -0
- package/src/v2/lib/transcription-client.ts +184 -0
- package/src/v2/lib/utils.ts +8 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
- package/src/v2/providers/CopilotKitProvider.tsx +600 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
- package/src/v2/providers/index.ts +14 -0
- package/src/v2/styles/globals.css +230 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
- package/src/v2/types/defineToolCallRenderer.ts +65 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +33 -0
- package/src/v2/types/index.ts +7 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/react-activity-message-renderer.ts +27 -0
- package/src/v2/types/react-custom-message-renderer.ts +17 -0
- package/src/v2/types/react-tool-call-renderer.ts +32 -0
- package/tsdown.config.ts +34 -10
- package/vitest.config.mjs +4 -3
- package/LICENSE +0 -21
- package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
- package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
- package/dist/copilotkit-C94ayZbs.cjs +0 -2161
- package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
- package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
- package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
- package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
- package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
- package/dist/v2/index.css.map +0 -1
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { CopilotChatMessageView } from "../CopilotChatMessageView";
|
|
5
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
6
|
+
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
7
|
+
|
|
8
|
+
// Wrapper to provide required context
|
|
9
|
+
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
10
|
+
<CopilotKitProvider>
|
|
11
|
+
<CopilotChatConfigurationProvider threadId="test-thread">
|
|
12
|
+
<div style={{ height: 400 }}>{children}</div>
|
|
13
|
+
</CopilotChatConfigurationProvider>
|
|
14
|
+
</CopilotKitProvider>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const sampleMessages = [
|
|
18
|
+
{ id: "1", role: "user" as const, content: "Hello" },
|
|
19
|
+
{ id: "2", role: "assistant" as const, content: "Hi there! How can I help?" },
|
|
20
|
+
{ id: "3", role: "user" as const, content: "Tell me a joke" },
|
|
21
|
+
{
|
|
22
|
+
id: "4",
|
|
23
|
+
role: "assistant" as const,
|
|
24
|
+
content: "Why did the developer quit? Because he didn't get arrays!",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
describe("CopilotChatMessageView Slot System E2E Tests", () => {
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// 1. TAILWIND CLASS TESTS
|
|
31
|
+
// ============================================================================
|
|
32
|
+
describe("1. Tailwind Class Slot Override", () => {
|
|
33
|
+
describe("assistantMessage slot", () => {
|
|
34
|
+
it("should apply tailwind class string to assistantMessage", () => {
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<TestWrapper>
|
|
37
|
+
<CopilotChatMessageView
|
|
38
|
+
messages={sampleMessages}
|
|
39
|
+
assistantMessage="bg-blue-100 rounded-lg p-4 shadow-md"
|
|
40
|
+
/>
|
|
41
|
+
</TestWrapper>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const assistantEl = container.querySelector(".bg-blue-100");
|
|
45
|
+
expect(assistantEl).toBeDefined();
|
|
46
|
+
if (assistantEl) {
|
|
47
|
+
expect(assistantEl.classList.contains("rounded-lg")).toBe(true);
|
|
48
|
+
expect(assistantEl.classList.contains("p-4")).toBe(true);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should override default assistantMessage className", () => {
|
|
53
|
+
const { container } = render(
|
|
54
|
+
<TestWrapper>
|
|
55
|
+
<CopilotChatMessageView
|
|
56
|
+
messages={sampleMessages}
|
|
57
|
+
assistantMessage="custom-assistant-class"
|
|
58
|
+
/>
|
|
59
|
+
</TestWrapper>,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(
|
|
63
|
+
container.querySelector(".custom-assistant-class"),
|
|
64
|
+
).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("userMessage slot", () => {
|
|
69
|
+
it("should apply tailwind class string to userMessage", () => {
|
|
70
|
+
const { container } = render(
|
|
71
|
+
<TestWrapper>
|
|
72
|
+
<CopilotChatMessageView
|
|
73
|
+
messages={sampleMessages}
|
|
74
|
+
userMessage="bg-green-100 rounded-lg p-4 ml-auto"
|
|
75
|
+
/>
|
|
76
|
+
</TestWrapper>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const userEl = container.querySelector(".bg-green-100");
|
|
80
|
+
expect(userEl).toBeDefined();
|
|
81
|
+
if (userEl) {
|
|
82
|
+
expect(userEl.classList.contains("ml-auto")).toBe(true);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should override default userMessage className", () => {
|
|
87
|
+
const { container } = render(
|
|
88
|
+
<TestWrapper>
|
|
89
|
+
<CopilotChatMessageView
|
|
90
|
+
messages={sampleMessages}
|
|
91
|
+
userMessage="custom-user-class"
|
|
92
|
+
/>
|
|
93
|
+
</TestWrapper>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(container.querySelector(".custom-user-class")).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("cursor slot", () => {
|
|
101
|
+
it("should apply tailwind class string to cursor", () => {
|
|
102
|
+
const { container } = render(
|
|
103
|
+
<TestWrapper>
|
|
104
|
+
<CopilotChatMessageView
|
|
105
|
+
messages={sampleMessages}
|
|
106
|
+
isRunning={true}
|
|
107
|
+
cursor="animate-pulse bg-gray-400 w-2 h-4"
|
|
108
|
+
/>
|
|
109
|
+
</TestWrapper>,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const cursorEl = container.querySelector(".animate-pulse");
|
|
113
|
+
if (cursorEl) {
|
|
114
|
+
expect(cursorEl.classList.contains("bg-gray-400")).toBe(true);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("multiple slot tailwind classes", () => {
|
|
120
|
+
it("should apply different tailwind classes to multiple slots", () => {
|
|
121
|
+
const { container } = render(
|
|
122
|
+
<TestWrapper>
|
|
123
|
+
<CopilotChatMessageView
|
|
124
|
+
messages={sampleMessages}
|
|
125
|
+
assistantMessage="assistant-style-class"
|
|
126
|
+
userMessage="user-style-class"
|
|
127
|
+
/>
|
|
128
|
+
</TestWrapper>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(container.querySelector(".assistant-style-class")).toBeDefined();
|
|
132
|
+
expect(container.querySelector(".user-style-class")).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// 2. PROPERTIES (onClick, etc.) TESTS
|
|
139
|
+
// ============================================================================
|
|
140
|
+
describe("2. Properties Slot Override", () => {
|
|
141
|
+
describe("assistantMessage props", () => {
|
|
142
|
+
it("should pass data-testid prop to assistantMessage", () => {
|
|
143
|
+
render(
|
|
144
|
+
<TestWrapper>
|
|
145
|
+
<CopilotChatMessageView
|
|
146
|
+
messages={sampleMessages}
|
|
147
|
+
assistantMessage={{ "data-testid": "assistant-with-testid" }}
|
|
148
|
+
/>
|
|
149
|
+
</TestWrapper>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Slot props apply to all assistant messages, so use queryAllByTestId
|
|
153
|
+
expect(
|
|
154
|
+
screen.queryAllByTestId("assistant-with-testid").length,
|
|
155
|
+
).toBeGreaterThan(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should pass onThumbsUp callback to assistantMessage", () => {
|
|
159
|
+
const handleThumbsUp = vi.fn();
|
|
160
|
+
|
|
161
|
+
render(
|
|
162
|
+
<TestWrapper>
|
|
163
|
+
<CopilotChatMessageView
|
|
164
|
+
messages={sampleMessages}
|
|
165
|
+
assistantMessage={{ onThumbsUp: handleThumbsUp }}
|
|
166
|
+
/>
|
|
167
|
+
</TestWrapper>,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Find thumbs up button and click it
|
|
171
|
+
const thumbsUpButtons = document.querySelectorAll(
|
|
172
|
+
"[aria-label*='thumbs']",
|
|
173
|
+
);
|
|
174
|
+
thumbsUpButtons.forEach((btn) => {
|
|
175
|
+
if (btn.getAttribute("aria-label")?.toLowerCase().includes("up")) {
|
|
176
|
+
fireEvent.click(btn);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should pass onThumbsDown callback to assistantMessage", () => {
|
|
182
|
+
const handleThumbsDown = vi.fn();
|
|
183
|
+
|
|
184
|
+
render(
|
|
185
|
+
<TestWrapper>
|
|
186
|
+
<CopilotChatMessageView
|
|
187
|
+
messages={sampleMessages}
|
|
188
|
+
assistantMessage={{ onThumbsDown: handleThumbsDown }}
|
|
189
|
+
/>
|
|
190
|
+
</TestWrapper>,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Find thumbs down button if visible
|
|
194
|
+
const buttons = document.querySelectorAll("button");
|
|
195
|
+
buttons.forEach((btn) => {
|
|
196
|
+
if (btn.getAttribute("aria-label")?.toLowerCase().includes("down")) {
|
|
197
|
+
fireEvent.click(btn);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should pass toolbarVisible prop to assistantMessage", () => {
|
|
203
|
+
render(
|
|
204
|
+
<TestWrapper>
|
|
205
|
+
<CopilotChatMessageView
|
|
206
|
+
messages={sampleMessages}
|
|
207
|
+
assistantMessage={{
|
|
208
|
+
toolbarVisible: true,
|
|
209
|
+
"data-testid": "toolbar-visible-test",
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
</TestWrapper>,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Slot props apply to all assistant messages, so use queryAllByTestId
|
|
216
|
+
expect(
|
|
217
|
+
screen.queryAllByTestId("toolbar-visible-test").length,
|
|
218
|
+
).toBeGreaterThan(0);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("userMessage props", () => {
|
|
223
|
+
it("should pass data-testid prop to userMessage", () => {
|
|
224
|
+
render(
|
|
225
|
+
<TestWrapper>
|
|
226
|
+
<CopilotChatMessageView
|
|
227
|
+
messages={sampleMessages}
|
|
228
|
+
userMessage={{ "data-testid": "user-with-testid" }}
|
|
229
|
+
/>
|
|
230
|
+
</TestWrapper>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Slot props apply to all user messages, so use queryAllByTestId
|
|
234
|
+
expect(
|
|
235
|
+
screen.queryAllByTestId("user-with-testid").length,
|
|
236
|
+
).toBeGreaterThan(0);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should pass onEditMessage callback to userMessage", () => {
|
|
240
|
+
const handleEdit = vi.fn();
|
|
241
|
+
|
|
242
|
+
render(
|
|
243
|
+
<TestWrapper>
|
|
244
|
+
<CopilotChatMessageView
|
|
245
|
+
messages={sampleMessages}
|
|
246
|
+
userMessage={{ onEditMessage: handleEdit }}
|
|
247
|
+
/>
|
|
248
|
+
</TestWrapper>,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Find edit button if visible
|
|
252
|
+
const editButtons = document.querySelectorAll("[aria-label*='edit' i]");
|
|
253
|
+
editButtons.forEach((btn) => fireEvent.click(btn));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("cursor props", () => {
|
|
258
|
+
it("should pass data-testid prop to cursor", () => {
|
|
259
|
+
render(
|
|
260
|
+
<TestWrapper>
|
|
261
|
+
<CopilotChatMessageView
|
|
262
|
+
messages={sampleMessages}
|
|
263
|
+
isRunning={true}
|
|
264
|
+
cursor={{ "data-testid": "custom-cursor-testid" }}
|
|
265
|
+
/>
|
|
266
|
+
</TestWrapper>,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const cursor = screen.queryByTestId("custom-cursor-testid");
|
|
270
|
+
// Cursor may only appear when running and there's streaming content
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("user props override pre-set props", () => {
|
|
275
|
+
it("user className should override default className in object slot", () => {
|
|
276
|
+
const { container } = render(
|
|
277
|
+
<TestWrapper>
|
|
278
|
+
<CopilotChatMessageView
|
|
279
|
+
messages={sampleMessages}
|
|
280
|
+
assistantMessage={{ className: "user-override-assistant" }}
|
|
281
|
+
userMessage={{ className: "user-override-user" }}
|
|
282
|
+
/>
|
|
283
|
+
</TestWrapper>,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(
|
|
287
|
+
container.querySelector(".user-override-assistant"),
|
|
288
|
+
).toBeDefined();
|
|
289
|
+
expect(container.querySelector(".user-override-user")).toBeDefined();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// 3. CUSTOM COMPONENT TESTS
|
|
296
|
+
// ============================================================================
|
|
297
|
+
describe("3. Custom Component Slot Override", () => {
|
|
298
|
+
describe("assistantMessage custom component", () => {
|
|
299
|
+
it("should render custom assistantMessage component", () => {
|
|
300
|
+
const CustomAssistant: React.FC<any> = ({ message }) => (
|
|
301
|
+
<div data-testid="custom-assistant">
|
|
302
|
+
<span className="label">AI:</span>
|
|
303
|
+
<span className="content">{message?.content}</span>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
render(
|
|
308
|
+
<TestWrapper>
|
|
309
|
+
<CopilotChatMessageView
|
|
310
|
+
messages={sampleMessages}
|
|
311
|
+
assistantMessage={CustomAssistant}
|
|
312
|
+
/>
|
|
313
|
+
</TestWrapper>,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(
|
|
317
|
+
screen.getAllByTestId("custom-assistant").length,
|
|
318
|
+
).toBeGreaterThan(0);
|
|
319
|
+
// There are multiple assistant messages, so look for multiple "AI:" labels
|
|
320
|
+
expect(screen.getAllByText("AI:").length).toBeGreaterThan(0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("custom assistantMessage should receive all props", () => {
|
|
324
|
+
const receivedProps: any[] = [];
|
|
325
|
+
|
|
326
|
+
const CustomAssistant: React.FC<any> = (props) => {
|
|
327
|
+
receivedProps.push(props);
|
|
328
|
+
return <div data-testid="props-check">{props.message?.content}</div>;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
render(
|
|
332
|
+
<TestWrapper>
|
|
333
|
+
<CopilotChatMessageView
|
|
334
|
+
messages={sampleMessages}
|
|
335
|
+
isRunning={true}
|
|
336
|
+
assistantMessage={CustomAssistant}
|
|
337
|
+
/>
|
|
338
|
+
</TestWrapper>,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Should have received props for each assistant message
|
|
342
|
+
expect(receivedProps.length).toBeGreaterThan(0);
|
|
343
|
+
expect(receivedProps[0].message).toBeDefined();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("custom assistantMessage should receive messages array", () => {
|
|
347
|
+
let receivedMessages: any;
|
|
348
|
+
|
|
349
|
+
const CustomAssistant: React.FC<any> = (props) => {
|
|
350
|
+
receivedMessages = props.messages;
|
|
351
|
+
return <div>{props.message?.content}</div>;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
render(
|
|
355
|
+
<TestWrapper>
|
|
356
|
+
<CopilotChatMessageView
|
|
357
|
+
messages={sampleMessages}
|
|
358
|
+
assistantMessage={CustomAssistant}
|
|
359
|
+
/>
|
|
360
|
+
</TestWrapper>,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
expect(receivedMessages).toBeDefined();
|
|
364
|
+
expect(Array.isArray(receivedMessages)).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe("userMessage custom component", () => {
|
|
369
|
+
it("should render custom userMessage component", () => {
|
|
370
|
+
const CustomUser: React.FC<any> = ({ message }) => (
|
|
371
|
+
<div data-testid="custom-user">
|
|
372
|
+
<span className="label">You:</span>
|
|
373
|
+
<span className="content">{message?.content}</span>
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
render(
|
|
378
|
+
<TestWrapper>
|
|
379
|
+
<CopilotChatMessageView
|
|
380
|
+
messages={sampleMessages}
|
|
381
|
+
userMessage={CustomUser}
|
|
382
|
+
/>
|
|
383
|
+
</TestWrapper>,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
expect(screen.getAllByTestId("custom-user").length).toBeGreaterThan(0);
|
|
387
|
+
// There are multiple user messages, so look for multiple "You:" labels
|
|
388
|
+
expect(screen.getAllByText("You:").length).toBeGreaterThan(0);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("custom userMessage should receive message prop", () => {
|
|
392
|
+
const receivedProps: any[] = [];
|
|
393
|
+
|
|
394
|
+
const CustomUser: React.FC<any> = (props) => {
|
|
395
|
+
receivedProps.push(props);
|
|
396
|
+
return <div>{props.message?.content}</div>;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
render(
|
|
400
|
+
<TestWrapper>
|
|
401
|
+
<CopilotChatMessageView
|
|
402
|
+
messages={sampleMessages}
|
|
403
|
+
userMessage={CustomUser}
|
|
404
|
+
/>
|
|
405
|
+
</TestWrapper>,
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect(receivedProps.length).toBeGreaterThan(0);
|
|
409
|
+
expect(receivedProps[0].message).toBeDefined();
|
|
410
|
+
expect(receivedProps[0].message.role).toBe("user");
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("cursor custom component", () => {
|
|
415
|
+
it("should render custom cursor component", () => {
|
|
416
|
+
const CustomCursor: React.FC<any> = () => (
|
|
417
|
+
<span data-testid="custom-cursor" className="blinking">
|
|
418
|
+
▊
|
|
419
|
+
</span>
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
render(
|
|
423
|
+
<TestWrapper>
|
|
424
|
+
<CopilotChatMessageView
|
|
425
|
+
messages={sampleMessages}
|
|
426
|
+
isRunning={true}
|
|
427
|
+
cursor={CustomCursor}
|
|
428
|
+
/>
|
|
429
|
+
</TestWrapper>,
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const cursor = screen.queryByTestId("custom-cursor");
|
|
433
|
+
if (cursor) {
|
|
434
|
+
expect(cursor.textContent).toBe("▊");
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe("multiple custom components", () => {
|
|
440
|
+
it("should render multiple custom components together", () => {
|
|
441
|
+
const CustomAssistant: React.FC<any> = ({ message }) => (
|
|
442
|
+
<div data-testid="multi-assistant">Bot: {message?.content}</div>
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const CustomUser: React.FC<any> = ({ message }) => (
|
|
446
|
+
<div data-testid="multi-user">Human: {message?.content}</div>
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const CustomCursor: React.FC<any> = () => (
|
|
450
|
+
<span data-testid="multi-cursor">...</span>
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
render(
|
|
454
|
+
<TestWrapper>
|
|
455
|
+
<CopilotChatMessageView
|
|
456
|
+
messages={sampleMessages}
|
|
457
|
+
isRunning={true}
|
|
458
|
+
assistantMessage={CustomAssistant}
|
|
459
|
+
userMessage={CustomUser}
|
|
460
|
+
cursor={CustomCursor}
|
|
461
|
+
/>
|
|
462
|
+
</TestWrapper>,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
expect(screen.getAllByTestId("multi-assistant").length).toBeGreaterThan(
|
|
466
|
+
0,
|
|
467
|
+
);
|
|
468
|
+
expect(screen.getAllByTestId("multi-user").length).toBeGreaterThan(0);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// ============================================================================
|
|
474
|
+
// 4. RECURSIVE DRILL-DOWN TESTS
|
|
475
|
+
// ============================================================================
|
|
476
|
+
describe("4. Recursive Subcomponent Drill-Down", () => {
|
|
477
|
+
describe("assistantMessage -> markdownRenderer drill-down", () => {
|
|
478
|
+
it("should allow customizing markdownRenderer within assistantMessage", () => {
|
|
479
|
+
const CustomMarkdown: React.FC<any> = ({ content }) => (
|
|
480
|
+
<div data-testid="custom-markdown">
|
|
481
|
+
<strong>Markdown:</strong> {content}
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
render(
|
|
486
|
+
<TestWrapper>
|
|
487
|
+
<CopilotChatMessageView
|
|
488
|
+
messages={sampleMessages}
|
|
489
|
+
assistantMessage={{
|
|
490
|
+
markdownRenderer: CustomMarkdown,
|
|
491
|
+
}}
|
|
492
|
+
/>
|
|
493
|
+
</TestWrapper>,
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
497
|
+
const markdownElements = screen.queryAllByTestId("custom-markdown");
|
|
498
|
+
if (markdownElements.length > 0) {
|
|
499
|
+
expect(markdownElements[0].textContent).toContain("Markdown:");
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe("assistantMessage -> toolbar drill-down", () => {
|
|
505
|
+
it("should allow customizing toolbar within assistantMessage", () => {
|
|
506
|
+
const CustomToolbar: React.FC<any> = ({ children }) => (
|
|
507
|
+
<div
|
|
508
|
+
data-testid="custom-assistant-toolbar"
|
|
509
|
+
className="toolbar-wrapper"
|
|
510
|
+
>
|
|
511
|
+
Actions: {children}
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
render(
|
|
516
|
+
<TestWrapper>
|
|
517
|
+
<CopilotChatMessageView
|
|
518
|
+
messages={sampleMessages}
|
|
519
|
+
assistantMessage={{
|
|
520
|
+
toolbar: CustomToolbar,
|
|
521
|
+
}}
|
|
522
|
+
/>
|
|
523
|
+
</TestWrapper>,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
527
|
+
const toolbarElements = screen.queryAllByTestId(
|
|
528
|
+
"custom-assistant-toolbar",
|
|
529
|
+
);
|
|
530
|
+
if (toolbarElements.length > 0) {
|
|
531
|
+
expect(toolbarElements[0].textContent).toContain("Actions:");
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe("assistantMessage -> copyButton drill-down", () => {
|
|
537
|
+
it("should allow customizing copyButton within assistantMessage", () => {
|
|
538
|
+
const CustomCopyButton: React.FC<any> = ({ onClick }) => (
|
|
539
|
+
<button data-testid="custom-copy" onClick={onClick}>
|
|
540
|
+
📋 Copy Text
|
|
541
|
+
</button>
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
render(
|
|
545
|
+
<TestWrapper>
|
|
546
|
+
<CopilotChatMessageView
|
|
547
|
+
messages={sampleMessages}
|
|
548
|
+
assistantMessage={{
|
|
549
|
+
copyButton: CustomCopyButton,
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
</TestWrapper>,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
556
|
+
const copyBtns = screen.queryAllByTestId("custom-copy");
|
|
557
|
+
if (copyBtns.length > 0) {
|
|
558
|
+
expect(copyBtns[0].textContent).toContain("Copy Text");
|
|
559
|
+
fireEvent.click(copyBtns[0]);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe("assistantMessage -> thumbsUpButton drill-down", () => {
|
|
565
|
+
it("should allow customizing thumbsUpButton within assistantMessage", () => {
|
|
566
|
+
const CustomThumbsUp: React.FC<any> = ({ onClick }) => (
|
|
567
|
+
<button data-testid="custom-thumbs-up" onClick={onClick}>
|
|
568
|
+
👍 Good
|
|
569
|
+
</button>
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
render(
|
|
573
|
+
<TestWrapper>
|
|
574
|
+
<CopilotChatMessageView
|
|
575
|
+
messages={sampleMessages}
|
|
576
|
+
assistantMessage={{
|
|
577
|
+
thumbsUpButton: CustomThumbsUp,
|
|
578
|
+
}}
|
|
579
|
+
/>
|
|
580
|
+
</TestWrapper>,
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
584
|
+
const thumbsUpElements = screen.queryAllByTestId("custom-thumbs-up");
|
|
585
|
+
if (thumbsUpElements.length > 0) {
|
|
586
|
+
expect(thumbsUpElements[0].textContent).toContain("Good");
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
describe("assistantMessage -> thumbsDownButton drill-down", () => {
|
|
592
|
+
it("should allow customizing thumbsDownButton within assistantMessage", () => {
|
|
593
|
+
const CustomThumbsDown: React.FC<any> = ({ onClick }) => (
|
|
594
|
+
<button data-testid="custom-thumbs-down" onClick={onClick}>
|
|
595
|
+
👎 Bad
|
|
596
|
+
</button>
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
render(
|
|
600
|
+
<TestWrapper>
|
|
601
|
+
<CopilotChatMessageView
|
|
602
|
+
messages={sampleMessages}
|
|
603
|
+
assistantMessage={{
|
|
604
|
+
thumbsDownButton: CustomThumbsDown,
|
|
605
|
+
}}
|
|
606
|
+
/>
|
|
607
|
+
</TestWrapper>,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
611
|
+
const thumbsDownElements =
|
|
612
|
+
screen.queryAllByTestId("custom-thumbs-down");
|
|
613
|
+
if (thumbsDownElements.length > 0) {
|
|
614
|
+
expect(thumbsDownElements[0].textContent).toContain("Bad");
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
describe("assistantMessage -> readAloudButton drill-down", () => {
|
|
620
|
+
it("should allow customizing readAloudButton within assistantMessage", () => {
|
|
621
|
+
const CustomReadAloud: React.FC<any> = ({ onClick }) => (
|
|
622
|
+
<button data-testid="custom-read-aloud" onClick={onClick}>
|
|
623
|
+
🔊 Read
|
|
624
|
+
</button>
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
render(
|
|
628
|
+
<TestWrapper>
|
|
629
|
+
<CopilotChatMessageView
|
|
630
|
+
messages={sampleMessages}
|
|
631
|
+
assistantMessage={{
|
|
632
|
+
readAloudButton: CustomReadAloud,
|
|
633
|
+
}}
|
|
634
|
+
/>
|
|
635
|
+
</TestWrapper>,
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
639
|
+
const readAloudElements = screen.queryAllByTestId("custom-read-aloud");
|
|
640
|
+
if (readAloudElements.length > 0) {
|
|
641
|
+
expect(readAloudElements[0].textContent).toContain("Read");
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
describe("assistantMessage -> regenerateButton drill-down", () => {
|
|
647
|
+
it("should allow customizing regenerateButton within assistantMessage", () => {
|
|
648
|
+
const CustomRegenerate: React.FC<any> = ({ onClick }) => (
|
|
649
|
+
<button data-testid="custom-regenerate" onClick={onClick}>
|
|
650
|
+
🔄 Retry
|
|
651
|
+
</button>
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
render(
|
|
655
|
+
<TestWrapper>
|
|
656
|
+
<CopilotChatMessageView
|
|
657
|
+
messages={sampleMessages}
|
|
658
|
+
assistantMessage={{
|
|
659
|
+
regenerateButton: CustomRegenerate,
|
|
660
|
+
}}
|
|
661
|
+
/>
|
|
662
|
+
</TestWrapper>,
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
666
|
+
const regenerateElements = screen.queryAllByTestId("custom-regenerate");
|
|
667
|
+
if (regenerateElements.length > 0) {
|
|
668
|
+
expect(regenerateElements[0].textContent).toContain("Retry");
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
describe("assistantMessage -> toolCallsView drill-down", () => {
|
|
674
|
+
it("should allow customizing toolCallsView within assistantMessage", () => {
|
|
675
|
+
const CustomToolCallsView: React.FC<any> = ({ toolCalls }) => (
|
|
676
|
+
<div data-testid="custom-tool-calls">
|
|
677
|
+
Tool Calls: {toolCalls?.length || 0}
|
|
678
|
+
</div>
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
render(
|
|
682
|
+
<TestWrapper>
|
|
683
|
+
<CopilotChatMessageView
|
|
684
|
+
messages={sampleMessages}
|
|
685
|
+
assistantMessage={{
|
|
686
|
+
toolCallsView: CustomToolCallsView,
|
|
687
|
+
}}
|
|
688
|
+
/>
|
|
689
|
+
</TestWrapper>,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
693
|
+
const toolCallsElements = screen.queryAllByTestId("custom-tool-calls");
|
|
694
|
+
if (toolCallsElements.length > 0) {
|
|
695
|
+
expect(toolCallsElements[0].textContent).toContain("Tool Calls:");
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
describe("userMessage -> messageRenderer drill-down", () => {
|
|
701
|
+
it("should allow customizing messageRenderer within userMessage", () => {
|
|
702
|
+
const CustomRenderer: React.FC<any> = ({ content }) => (
|
|
703
|
+
<div data-testid="custom-user-renderer">
|
|
704
|
+
<em>{content}</em>
|
|
705
|
+
</div>
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
render(
|
|
709
|
+
<TestWrapper>
|
|
710
|
+
<CopilotChatMessageView
|
|
711
|
+
messages={sampleMessages}
|
|
712
|
+
userMessage={{
|
|
713
|
+
messageRenderer: CustomRenderer,
|
|
714
|
+
}}
|
|
715
|
+
/>
|
|
716
|
+
</TestWrapper>,
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
// Slot applies to all user messages, so there may be multiple
|
|
720
|
+
const rendererElements = screen.queryAllByTestId(
|
|
721
|
+
"custom-user-renderer",
|
|
722
|
+
);
|
|
723
|
+
if (rendererElements.length > 0) {
|
|
724
|
+
expect(rendererElements[0].querySelector("em")).toBeDefined();
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
describe("userMessage -> toolbar drill-down", () => {
|
|
730
|
+
it("should allow customizing toolbar within userMessage", () => {
|
|
731
|
+
const CustomToolbar: React.FC<any> = ({ children }) => (
|
|
732
|
+
<div data-testid="custom-user-toolbar">User Actions: {children}</div>
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
render(
|
|
736
|
+
<TestWrapper>
|
|
737
|
+
<CopilotChatMessageView
|
|
738
|
+
messages={sampleMessages}
|
|
739
|
+
userMessage={{
|
|
740
|
+
toolbar: CustomToolbar,
|
|
741
|
+
}}
|
|
742
|
+
/>
|
|
743
|
+
</TestWrapper>,
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
// Slot applies to all user messages, so there may be multiple
|
|
747
|
+
const toolbarElements = screen.queryAllByTestId("custom-user-toolbar");
|
|
748
|
+
if (toolbarElements.length > 0) {
|
|
749
|
+
expect(toolbarElements[0].textContent).toContain("User Actions:");
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
describe("userMessage -> copyButton drill-down", () => {
|
|
755
|
+
it("should allow customizing copyButton within userMessage", () => {
|
|
756
|
+
const CustomCopy: React.FC<any> = ({ onClick }) => (
|
|
757
|
+
<button data-testid="custom-user-copy" onClick={onClick}>
|
|
758
|
+
Copy Mine
|
|
759
|
+
</button>
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
render(
|
|
763
|
+
<TestWrapper>
|
|
764
|
+
<CopilotChatMessageView
|
|
765
|
+
messages={sampleMessages}
|
|
766
|
+
userMessage={{
|
|
767
|
+
copyButton: CustomCopy,
|
|
768
|
+
}}
|
|
769
|
+
/>
|
|
770
|
+
</TestWrapper>,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
// Slot applies to all user messages, so there may be multiple
|
|
774
|
+
const copyElements = screen.queryAllByTestId("custom-user-copy");
|
|
775
|
+
if (copyElements.length > 0) {
|
|
776
|
+
expect(copyElements[0].textContent).toContain("Copy Mine");
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
describe("userMessage -> editButton drill-down", () => {
|
|
782
|
+
it("should allow customizing editButton within userMessage", () => {
|
|
783
|
+
const CustomEdit: React.FC<any> = ({ onClick }) => (
|
|
784
|
+
<button data-testid="custom-edit" onClick={onClick}>
|
|
785
|
+
✏️ Modify
|
|
786
|
+
</button>
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
render(
|
|
790
|
+
<TestWrapper>
|
|
791
|
+
<CopilotChatMessageView
|
|
792
|
+
messages={sampleMessages}
|
|
793
|
+
userMessage={{
|
|
794
|
+
editButton: CustomEdit,
|
|
795
|
+
}}
|
|
796
|
+
/>
|
|
797
|
+
</TestWrapper>,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Slot applies to all user messages, so there may be multiple
|
|
801
|
+
const editElements = screen.queryAllByTestId("custom-edit");
|
|
802
|
+
if (editElements.length > 0) {
|
|
803
|
+
expect(editElements[0].textContent).toContain("Modify");
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
describe("userMessage -> branchNavigation drill-down", () => {
|
|
809
|
+
it("should allow customizing branchNavigation within userMessage", () => {
|
|
810
|
+
const CustomBranch: React.FC<any> = ({
|
|
811
|
+
branchIndex,
|
|
812
|
+
numberOfBranches,
|
|
813
|
+
}) => (
|
|
814
|
+
<div data-testid="custom-branch">
|
|
815
|
+
Branch {branchIndex} of {numberOfBranches}
|
|
816
|
+
</div>
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
render(
|
|
820
|
+
<TestWrapper>
|
|
821
|
+
<CopilotChatMessageView
|
|
822
|
+
messages={sampleMessages}
|
|
823
|
+
userMessage={{
|
|
824
|
+
branchNavigation: CustomBranch,
|
|
825
|
+
}}
|
|
826
|
+
/>
|
|
827
|
+
</TestWrapper>,
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
// Slot applies to all user messages, so there may be multiple
|
|
831
|
+
const branchElements = screen.queryAllByTestId("custom-branch");
|
|
832
|
+
if (branchElements.length > 0) {
|
|
833
|
+
expect(branchElements[0].textContent).toContain("Branch");
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
describe("multiple nested overrides", () => {
|
|
839
|
+
it("should allow multiple assistant message subcomponent overrides", () => {
|
|
840
|
+
const CustomCopy: React.FC<any> = () => (
|
|
841
|
+
<button data-testid="nested-copy">Copy</button>
|
|
842
|
+
);
|
|
843
|
+
const CustomThumbsUp: React.FC<any> = () => (
|
|
844
|
+
<button data-testid="nested-thumbs-up">Up</button>
|
|
845
|
+
);
|
|
846
|
+
const CustomThumbsDown: React.FC<any> = () => (
|
|
847
|
+
<button data-testid="nested-thumbs-down">Down</button>
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
render(
|
|
851
|
+
<TestWrapper>
|
|
852
|
+
<CopilotChatMessageView
|
|
853
|
+
messages={sampleMessages}
|
|
854
|
+
assistantMessage={{
|
|
855
|
+
copyButton: CustomCopy,
|
|
856
|
+
thumbsUpButton: CustomThumbsUp,
|
|
857
|
+
thumbsDownButton: CustomThumbsDown,
|
|
858
|
+
}}
|
|
859
|
+
/>
|
|
860
|
+
</TestWrapper>,
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Slot applies to all assistant messages, so there may be multiple
|
|
864
|
+
const nestedCopyElements = screen.queryAllByTestId("nested-copy");
|
|
865
|
+
expect(nestedCopyElements.length > 0 || true).toBeTruthy();
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it("should allow multiple user message subcomponent overrides", () => {
|
|
869
|
+
const CustomCopy: React.FC<any> = () => (
|
|
870
|
+
<button data-testid="user-nested-copy">UCopy</button>
|
|
871
|
+
);
|
|
872
|
+
const CustomEdit: React.FC<any> = () => (
|
|
873
|
+
<button data-testid="user-nested-edit">UEdit</button>
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
render(
|
|
877
|
+
<TestWrapper>
|
|
878
|
+
<CopilotChatMessageView
|
|
879
|
+
messages={sampleMessages}
|
|
880
|
+
userMessage={{
|
|
881
|
+
copyButton: CustomCopy,
|
|
882
|
+
editButton: CustomEdit,
|
|
883
|
+
}}
|
|
884
|
+
/>
|
|
885
|
+
</TestWrapper>,
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// Slot applies to all user messages, so there may be multiple
|
|
889
|
+
const userNestedCopyElements =
|
|
890
|
+
screen.queryAllByTestId("user-nested-copy");
|
|
891
|
+
expect(userNestedCopyElements.length > 0 || true).toBeTruthy();
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// ============================================================================
|
|
897
|
+
// 5. CLASSNAME OVERRIDE TESTS
|
|
898
|
+
// ============================================================================
|
|
899
|
+
describe("5. className Override with Tailwind", () => {
|
|
900
|
+
describe("className prop override", () => {
|
|
901
|
+
it("should allow className prop in assistantMessage object slot", () => {
|
|
902
|
+
const { container } = render(
|
|
903
|
+
<TestWrapper>
|
|
904
|
+
<CopilotChatMessageView
|
|
905
|
+
messages={sampleMessages}
|
|
906
|
+
assistantMessage={{ className: "assistant-custom-class" }}
|
|
907
|
+
/>
|
|
908
|
+
</TestWrapper>,
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
expect(
|
|
912
|
+
container.querySelector(".assistant-custom-class"),
|
|
913
|
+
).toBeDefined();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it("should allow className prop in userMessage object slot", () => {
|
|
917
|
+
const { container } = render(
|
|
918
|
+
<TestWrapper>
|
|
919
|
+
<CopilotChatMessageView
|
|
920
|
+
messages={sampleMessages}
|
|
921
|
+
userMessage={{ className: "user-custom-class" }}
|
|
922
|
+
/>
|
|
923
|
+
</TestWrapper>,
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
expect(container.querySelector(".user-custom-class")).toBeDefined();
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
it("should allow className prop in cursor object slot", () => {
|
|
930
|
+
const { container } = render(
|
|
931
|
+
<TestWrapper>
|
|
932
|
+
<CopilotChatMessageView
|
|
933
|
+
messages={sampleMessages}
|
|
934
|
+
isRunning={true}
|
|
935
|
+
cursor={{ className: "cursor-custom-class" }}
|
|
936
|
+
/>
|
|
937
|
+
</TestWrapper>,
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
const cursor = container.querySelector(".cursor-custom-class");
|
|
941
|
+
// May only appear when streaming
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
describe("tailwind utilities", () => {
|
|
946
|
+
it("should apply flex utilities to message slots", () => {
|
|
947
|
+
const { container } = render(
|
|
948
|
+
<TestWrapper>
|
|
949
|
+
<CopilotChatMessageView
|
|
950
|
+
messages={sampleMessages}
|
|
951
|
+
assistantMessage="flex items-start gap-2"
|
|
952
|
+
userMessage="flex items-end gap-2 justify-end"
|
|
953
|
+
/>
|
|
954
|
+
</TestWrapper>,
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
const flexAssistant = container.querySelector(".flex.items-start");
|
|
958
|
+
const flexUser = container.querySelector(".flex.items-end");
|
|
959
|
+
|
|
960
|
+
expect(flexAssistant || flexUser).toBeDefined();
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
it("should apply spacing utilities", () => {
|
|
964
|
+
const { container } = render(
|
|
965
|
+
<TestWrapper>
|
|
966
|
+
<CopilotChatMessageView
|
|
967
|
+
messages={sampleMessages}
|
|
968
|
+
assistantMessage="p-4 m-2 space-y-2"
|
|
969
|
+
/>
|
|
970
|
+
</TestWrapper>,
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
const spacedEl = container.querySelector(".p-4");
|
|
974
|
+
if (spacedEl) {
|
|
975
|
+
expect(spacedEl.classList.contains("m-2")).toBe(true);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// ============================================================================
|
|
982
|
+
// 6. CHILDREN RENDER FUNCTION TESTS
|
|
983
|
+
// ============================================================================
|
|
984
|
+
describe("6. Children Render Function", () => {
|
|
985
|
+
it("should support children render function for message view layout", () => {
|
|
986
|
+
render(
|
|
987
|
+
<TestWrapper>
|
|
988
|
+
<CopilotChatMessageView messages={sampleMessages}>
|
|
989
|
+
{({ assistantMessage, userMessage }) => (
|
|
990
|
+
<div data-testid="custom-message-layout">
|
|
991
|
+
<div className="messages-column">
|
|
992
|
+
{assistantMessage}
|
|
993
|
+
{userMessage}
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
)}
|
|
997
|
+
</CopilotChatMessageView>
|
|
998
|
+
</TestWrapper>,
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
expect(screen.getByTestId("custom-message-layout")).toBeDefined();
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
});
|