@copilotkit/react-core 1.56.3 → 1.56.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/dist/{copilotkit-PzJlPKcU.mjs → copilotkit-Bd0m5HFp.mjs} +24 -26
- package/dist/{copilotkit-PzJlPKcU.mjs.map → copilotkit-Bd0m5HFp.mjs.map} +1 -1
- package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -1
- package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -1
- package/dist/{copilotkit-By2G6-Zx.cjs → copilotkit-tb4zqaMK.cjs} +24 -26
- package/dist/{copilotkit-By2G6-Zx.cjs.map → copilotkit-tb4zqaMK.cjs.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.css +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/index.umd.js +23 -25
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +6 -6
- package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +4 -1
- package/src/v2/components/chat/CopilotChatView.tsx +41 -73
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +193 -57
- package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +172 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { render, waitFor } from "@testing-library/react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
5
|
+
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
6
|
+
import { CopilotChatView } from "../CopilotChatView";
|
|
7
|
+
import { LastUserMessageContext } from "../last-user-message-context";
|
|
8
|
+
import type { Attachment } from "@copilotkit/shared";
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
HTMLElement.prototype.scrollTo = vi.fn();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
15
|
+
<CopilotKitProvider>
|
|
16
|
+
<CopilotChatConfigurationProvider threadId="test-thread">
|
|
17
|
+
<div style={{ height: 400 }}>{children}</div>
|
|
18
|
+
</CopilotChatConfigurationProvider>
|
|
19
|
+
</CopilotKitProvider>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const sampleMessages = [
|
|
23
|
+
{ id: "1", role: "user" as const, content: "Hello" },
|
|
24
|
+
{ id: "2", role: "assistant" as const, content: "Hi there!" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const sampleAttachments: Attachment[] = [
|
|
28
|
+
{
|
|
29
|
+
id: "att-1",
|
|
30
|
+
type: "document",
|
|
31
|
+
source: {
|
|
32
|
+
type: "url",
|
|
33
|
+
value: "https://example.com/doc.txt",
|
|
34
|
+
mimeType: "text/plain",
|
|
35
|
+
},
|
|
36
|
+
filename: "example.txt",
|
|
37
|
+
size: 42,
|
|
38
|
+
status: "ready",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
async function waitForMount(screen: {
|
|
43
|
+
findByTestId: (id: string) => Promise<HTMLElement>;
|
|
44
|
+
}) {
|
|
45
|
+
await screen.findByTestId("copilot-message-list");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("CopilotChatView input overlay layout", () => {
|
|
49
|
+
it("renders the input inside an absolute-positioned overlay wrapper on the main view", async () => {
|
|
50
|
+
const screen = render(
|
|
51
|
+
<TestWrapper>
|
|
52
|
+
<LastUserMessageContext.Provider value={{ id: null, sendNonce: 0 }}>
|
|
53
|
+
<CopilotChatView messages={sampleMessages} />
|
|
54
|
+
</LastUserMessageContext.Provider>
|
|
55
|
+
</TestWrapper>,
|
|
56
|
+
);
|
|
57
|
+
await waitForMount(screen);
|
|
58
|
+
|
|
59
|
+
// getByTestId throws if missing — presence is implicit.
|
|
60
|
+
const overlay = screen.getByTestId("copilot-input-overlay");
|
|
61
|
+
// Class-level assertion — the cpk: prefix avoids false positives from
|
|
62
|
+
// consumer classes. Absolute + bottom-0 is the contract we care about.
|
|
63
|
+
expect(overlay.className).toMatch(/cpk:absolute/);
|
|
64
|
+
expect(overlay.className).toMatch(/cpk:bottom-0/);
|
|
65
|
+
|
|
66
|
+
// Input (send button) lives inside the overlay, not outside it.
|
|
67
|
+
const sendButton = screen.getByTestId("copilot-send-button");
|
|
68
|
+
expect(overlay.contains(sendButton)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders the attachment queue above the input inside the overlay wrapper", async () => {
|
|
72
|
+
const screen = render(
|
|
73
|
+
<TestWrapper>
|
|
74
|
+
<LastUserMessageContext.Provider value={{ id: null, sendNonce: 0 }}>
|
|
75
|
+
<CopilotChatView
|
|
76
|
+
messages={sampleMessages}
|
|
77
|
+
attachments={sampleAttachments}
|
|
78
|
+
/>
|
|
79
|
+
</LastUserMessageContext.Provider>
|
|
80
|
+
</TestWrapper>,
|
|
81
|
+
);
|
|
82
|
+
await waitForMount(screen);
|
|
83
|
+
|
|
84
|
+
const overlay = screen.getByTestId("copilot-input-overlay");
|
|
85
|
+
const queue = overlay.querySelector(
|
|
86
|
+
'[data-testid="copilot-attachment-queue"]',
|
|
87
|
+
);
|
|
88
|
+
const sendButton = overlay.querySelector(
|
|
89
|
+
'[data-testid="copilot-send-button"]',
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(queue).not.toBeNull();
|
|
93
|
+
expect(sendButton).not.toBeNull();
|
|
94
|
+
|
|
95
|
+
// DOM order: the attachment queue must appear before the send button
|
|
96
|
+
// in document order so it renders visually above the pill.
|
|
97
|
+
const position = queue!.compareDocumentPosition(sendButton!);
|
|
98
|
+
expect(position & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("does NOT wrap the welcome-screen input in the overlay", async () => {
|
|
102
|
+
const screen = render(
|
|
103
|
+
<TestWrapper>
|
|
104
|
+
<LastUserMessageContext.Provider value={{ id: null, sendNonce: 0 }}>
|
|
105
|
+
<CopilotChatView messages={[]} />
|
|
106
|
+
</LastUserMessageContext.Provider>
|
|
107
|
+
</TestWrapper>,
|
|
108
|
+
);
|
|
109
|
+
await screen.findByTestId("copilot-welcome-screen");
|
|
110
|
+
|
|
111
|
+
// Welcome screen present → no overlay wrapper exists in this render.
|
|
112
|
+
expect(screen.queryByTestId("copilot-input-overlay")).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("reserves inputContainerHeight as bottom padding on the scroll content", async () => {
|
|
116
|
+
// Spy on ResizeObserver so we can trigger a known height. The component
|
|
117
|
+
// uses ResizeObserver to measure the overlay wrapper; we inject a known
|
|
118
|
+
// value and assert the scroll content's inline padding-bottom reflects it.
|
|
119
|
+
const callbacks: Array<{
|
|
120
|
+
cb: ResizeObserverCallback;
|
|
121
|
+
target: Element | null;
|
|
122
|
+
}> = [];
|
|
123
|
+
const OriginalRO = global.ResizeObserver;
|
|
124
|
+
class MockResizeObserver {
|
|
125
|
+
private cb: ResizeObserverCallback;
|
|
126
|
+
constructor(cb: ResizeObserverCallback) {
|
|
127
|
+
this.cb = cb;
|
|
128
|
+
}
|
|
129
|
+
observe(target: Element) {
|
|
130
|
+
callbacks.push({ cb: this.cb, target });
|
|
131
|
+
}
|
|
132
|
+
unobserve() {}
|
|
133
|
+
disconnect() {}
|
|
134
|
+
}
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
(global as any).ResizeObserver = MockResizeObserver as any;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const screen = render(
|
|
140
|
+
<TestWrapper>
|
|
141
|
+
<LastUserMessageContext.Provider value={{ id: null, sendNonce: 0 }}>
|
|
142
|
+
<CopilotChatView messages={sampleMessages} />
|
|
143
|
+
</LastUserMessageContext.Provider>
|
|
144
|
+
</TestWrapper>,
|
|
145
|
+
);
|
|
146
|
+
await waitForMount(screen);
|
|
147
|
+
|
|
148
|
+
const scrollContent = screen.getByTestId("copilot-scroll-content");
|
|
149
|
+
|
|
150
|
+
// Simulate the overlay wrapper reporting a content height of 120px.
|
|
151
|
+
for (const { cb } of callbacks) {
|
|
152
|
+
cb(
|
|
153
|
+
[
|
|
154
|
+
{
|
|
155
|
+
contentRect: { height: 120 } as DOMRectReadOnly,
|
|
156
|
+
} as ResizeObserverEntry,
|
|
157
|
+
],
|
|
158
|
+
{} as ResizeObserver,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// After the resize fires, paddingBottom = 120 (input) + 32 (baseline,
|
|
163
|
+
// no suggestions) = "152px". The test asserts the formula.
|
|
164
|
+
await waitFor(() =>
|
|
165
|
+
expect(scrollContent.style.paddingBottom).toBe("152px"),
|
|
166
|
+
);
|
|
167
|
+
} finally {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
|
+
(global as any).ResizeObserver = OriginalRO;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|