@copilotkit/react-core 1.56.2 → 1.56.4-canary.1777529757
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-CSJw5BG8.cjs → copilotkit-BAkj3zUc.cjs} +359 -157
- package/dist/copilotkit-BAkj3zUc.cjs.map +1 -0
- package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-DAatqMh2.mjs} +360 -158
- package/dist/copilotkit-DAatqMh2.mjs.map +1 -0
- package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DFaI4j2r.d.mts} +64 -18
- package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -0
- package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-Dg4r4Gi_.d.cts} +64 -18
- package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.umd.js +31 -44
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.css +1 -1
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/index.umd.js +361 -163
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +8 -8
- package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +107 -0
- package/src/components/copilot-provider/copilotkit.tsx +6 -1
- package/src/context/__tests__/threads-context.test.tsx +116 -3
- package/src/context/threads-context.tsx +18 -1
- package/src/v2/components/chat/CopilotChat.tsx +91 -4
- package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +7 -114
- package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +26 -6
- package/src/v2/components/chat/CopilotChatInput.tsx +22 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +2 -2
- package/src/v2/components/chat/CopilotChatView.tsx +226 -48
- package/src/v2/components/chat/Lightbox.tsx +103 -0
- package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
- package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +189 -0
- package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +186 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +438 -4
- package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +264 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
- package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
- package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
- package/src/v2/components/chat/index.ts +2 -0
- package/src/v2/components/chat/last-user-message-context.ts +21 -0
- package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
- package/src/v2/components/license-warning-banner.tsx +20 -1
- package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
- package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
- package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
- package/src/v2/hooks/use-agent.tsx +34 -77
- package/src/v2/hooks/use-pin-to-send.ts +94 -0
- package/src/v2/hooks/use-threads.tsx +55 -12
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +29 -1
- package/src/v2/providers/CopilotKitProvider.tsx +2 -11
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +106 -0
- package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
- package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
- package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
- package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { EventType } from "@ag-ui/client";
|
|
4
5
|
import {
|
|
6
|
+
MockReconnectableAgent,
|
|
5
7
|
MockStepwiseAgent,
|
|
6
8
|
activitySnapshotEvent,
|
|
7
9
|
renderWithCopilotKit,
|
|
@@ -9,10 +11,164 @@ import {
|
|
|
9
11
|
runStartedEvent,
|
|
10
12
|
testId,
|
|
11
13
|
} from "../../../__tests__/utils/test-helpers";
|
|
12
|
-
import { ReactActivityMessageRenderer } from "../../../types";
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import type { ReactActivityMessageRenderer } from "../../../types";
|
|
15
|
+
import {
|
|
16
|
+
CopilotChatConfigurationProvider,
|
|
17
|
+
CopilotKitProvider,
|
|
18
|
+
useCopilotKit,
|
|
19
|
+
} from "../../../providers";
|
|
20
|
+
import type { AbstractAgent } from "@ag-ui/client";
|
|
21
|
+
import { IntelligenceAgent } from "@copilotkit/core";
|
|
15
22
|
import { getThreadClone } from "../../../hooks/use-agent";
|
|
23
|
+
import { createA2UIMessageRenderer } from "../../../a2ui/A2UIMessageRenderer";
|
|
24
|
+
import type { Theme } from "@copilotkit/a2ui-renderer";
|
|
25
|
+
import { CopilotChat } from "..";
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
mockWebsandboxCreate,
|
|
29
|
+
mockWebsandboxDestroy,
|
|
30
|
+
mockPhoenixSockets,
|
|
31
|
+
MockPhoenixSocket,
|
|
32
|
+
} = vi.hoisted(() => {
|
|
33
|
+
const mockDestroy = vi.fn();
|
|
34
|
+
const mockCreate = vi.fn((..._args: unknown[]) => ({
|
|
35
|
+
iframe: document.createElement("iframe"),
|
|
36
|
+
promise: Promise.resolve(),
|
|
37
|
+
run: vi.fn().mockResolvedValue(undefined),
|
|
38
|
+
destroy: mockDestroy,
|
|
39
|
+
}));
|
|
40
|
+
const mockSockets: MockPhoenixSocket[] = [];
|
|
41
|
+
|
|
42
|
+
class MockPhoenixPush {
|
|
43
|
+
private callbacks = new Map<string, (response?: unknown) => void>();
|
|
44
|
+
|
|
45
|
+
receive(
|
|
46
|
+
status: string,
|
|
47
|
+
callback: (response?: unknown) => void,
|
|
48
|
+
): MockPhoenixPush {
|
|
49
|
+
this.callbacks.set(status, callback);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
trigger(status: string, response?: unknown): void {
|
|
54
|
+
this.callbacks.get(status)?.(response);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class MockPhoenixChannel {
|
|
59
|
+
public topic: string;
|
|
60
|
+
public params: Record<string, unknown>;
|
|
61
|
+
public left = false;
|
|
62
|
+
|
|
63
|
+
private handlers = new Map<
|
|
64
|
+
string,
|
|
65
|
+
Array<{ ref: number; callback: (payload: unknown) => void }>
|
|
66
|
+
>();
|
|
67
|
+
private joinPush = new MockPhoenixPush();
|
|
68
|
+
private nextRef = 1;
|
|
69
|
+
|
|
70
|
+
constructor(topic: string, params: Record<string, unknown>) {
|
|
71
|
+
this.topic = topic;
|
|
72
|
+
this.params = params;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
on(event: string, callback: (payload: unknown) => void): number {
|
|
76
|
+
if (!this.handlers.has(event)) {
|
|
77
|
+
this.handlers.set(event, []);
|
|
78
|
+
}
|
|
79
|
+
const ref = this.nextRef;
|
|
80
|
+
this.nextRef += 1;
|
|
81
|
+
this.handlers.get(event)?.push({ ref, callback });
|
|
82
|
+
return ref;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
off(event: string, ref?: number): void {
|
|
86
|
+
if (ref === undefined) {
|
|
87
|
+
this.handlers.delete(event);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.handlers.set(
|
|
91
|
+
event,
|
|
92
|
+
(this.handlers.get(event) ?? []).filter(
|
|
93
|
+
(handler) => handler.ref !== ref,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
join(): MockPhoenixPush {
|
|
99
|
+
return this.joinPush;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
leave(): void {
|
|
103
|
+
this.left = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
triggerJoin(status: string, response?: unknown): void {
|
|
107
|
+
this.joinPush.trigger(status, response);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
serverPush(event: string, payload: unknown): void {
|
|
111
|
+
for (const { callback } of this.handlers.get(event) ?? []) {
|
|
112
|
+
callback(payload);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class MockPhoenixSocket {
|
|
118
|
+
public channels: MockPhoenixChannel[] = [];
|
|
119
|
+
|
|
120
|
+
constructor(
|
|
121
|
+
public url: string,
|
|
122
|
+
public opts: Record<string, unknown>,
|
|
123
|
+
) {
|
|
124
|
+
mockSockets.push(this);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
connect(): void {}
|
|
128
|
+
|
|
129
|
+
disconnect(): void {}
|
|
130
|
+
|
|
131
|
+
onOpen(): void {}
|
|
132
|
+
|
|
133
|
+
onError(): void {}
|
|
134
|
+
|
|
135
|
+
channel(
|
|
136
|
+
topic: string,
|
|
137
|
+
params: Record<string, unknown>,
|
|
138
|
+
): MockPhoenixChannel {
|
|
139
|
+
const channel = new MockPhoenixChannel(topic, params);
|
|
140
|
+
this.channels.push(channel);
|
|
141
|
+
return channel;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
mockWebsandboxCreate: mockCreate,
|
|
147
|
+
mockWebsandboxDestroy: mockDestroy,
|
|
148
|
+
mockPhoenixSockets: mockSockets,
|
|
149
|
+
MockPhoenixSocket,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
vi.mock("phoenix", () => ({
|
|
154
|
+
Socket: MockPhoenixSocket,
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
vi.mock("@jetbrains/websandbox", () => ({
|
|
158
|
+
default: {
|
|
159
|
+
create: (...args: unknown[]) => mockWebsandboxCreate(...args),
|
|
160
|
+
},
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
164
|
+
return {
|
|
165
|
+
ok: status >= 200 && status < 300,
|
|
166
|
+
status,
|
|
167
|
+
statusText: status === 200 ? "OK" : "Error",
|
|
168
|
+
json: async () => body,
|
|
169
|
+
text: async () => JSON.stringify(body),
|
|
170
|
+
} as Response;
|
|
171
|
+
}
|
|
16
172
|
|
|
17
173
|
describe("CopilotChat activity message rendering", () => {
|
|
18
174
|
it("renders custom components for activity snapshots", async () => {
|
|
@@ -208,4 +364,282 @@ describe("CopilotChat activity message rendering", () => {
|
|
|
208
364
|
expect(capturedAgent).toBe(clone);
|
|
209
365
|
expect(capturedAgent).not.toBe(agent); // must NOT be the registry agent
|
|
210
366
|
});
|
|
367
|
+
|
|
368
|
+
it("restores a completed A2UI surface after reconnect from an event-native baseline", async () => {
|
|
369
|
+
const agent = new MockReconnectableAgent();
|
|
370
|
+
const threadId = testId("a2ui-thread");
|
|
371
|
+
const surfaceId = testId("surface");
|
|
372
|
+
const a2uiRenderer = createA2UIMessageRenderer({
|
|
373
|
+
theme: {} as Theme,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const { unmount } = renderWithCopilotKit({
|
|
377
|
+
agent,
|
|
378
|
+
threadId,
|
|
379
|
+
renderActivityMessages: [a2uiRenderer],
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const input = await screen.findByRole("textbox");
|
|
383
|
+
fireEvent.change(input, { target: { value: "Show me the restored UI" } });
|
|
384
|
+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
385
|
+
|
|
386
|
+
await waitFor(() => {
|
|
387
|
+
expect(screen.getByText("Show me the restored UI")).toBeDefined();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
agent.emit(runStartedEvent());
|
|
391
|
+
agent.emit(
|
|
392
|
+
activitySnapshotEvent({
|
|
393
|
+
messageId: testId("a2ui-activity"),
|
|
394
|
+
activityType: "a2ui-surface",
|
|
395
|
+
content: {
|
|
396
|
+
a2ui_operations: [
|
|
397
|
+
{
|
|
398
|
+
version: "v0.9",
|
|
399
|
+
createSurface: {
|
|
400
|
+
surfaceId,
|
|
401
|
+
catalogId:
|
|
402
|
+
"https://a2ui.org/specification/v0_9/basic_catalog.json",
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
version: "v0.9",
|
|
407
|
+
updateComponents: {
|
|
408
|
+
surfaceId,
|
|
409
|
+
components: [
|
|
410
|
+
{
|
|
411
|
+
id: "root",
|
|
412
|
+
component: "Text",
|
|
413
|
+
text: "Restored dashboard",
|
|
414
|
+
variant: "body",
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
}),
|
|
422
|
+
);
|
|
423
|
+
agent.emit(runFinishedEvent());
|
|
424
|
+
agent.complete();
|
|
425
|
+
|
|
426
|
+
await waitFor(() => {
|
|
427
|
+
expect(
|
|
428
|
+
document.querySelector(`[data-surface-id='${surfaceId}']`),
|
|
429
|
+
).not.toBeNull();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
unmount();
|
|
433
|
+
agent.reset();
|
|
434
|
+
|
|
435
|
+
renderWithCopilotKit({
|
|
436
|
+
agent,
|
|
437
|
+
threadId,
|
|
438
|
+
renderActivityMessages: [a2uiRenderer],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await waitFor(() => {
|
|
442
|
+
expect(
|
|
443
|
+
document.querySelector(`[data-surface-id='${surfaceId}']`),
|
|
444
|
+
).not.toBeNull();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("restores a completed A2UI surface from IntelligenceAgent /connect gateway replay", async () => {
|
|
449
|
+
const threadId = testId("intelligence-connect-thread");
|
|
450
|
+
const surfaceId = testId("intelligence-connect-surface");
|
|
451
|
+
const fetchMock = vi.fn().mockResolvedValueOnce(
|
|
452
|
+
jsonResponse({
|
|
453
|
+
threadId,
|
|
454
|
+
runId: null,
|
|
455
|
+
joinToken: "join-token-1",
|
|
456
|
+
realtime: {
|
|
457
|
+
clientUrl: "ws://localhost:4000/client",
|
|
458
|
+
topic: `thread:${threadId}`,
|
|
459
|
+
},
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
463
|
+
mockPhoenixSockets.length = 0;
|
|
464
|
+
|
|
465
|
+
const agent = new IntelligenceAgent({
|
|
466
|
+
url: "ws://localhost:4000/client",
|
|
467
|
+
runtimeUrl: "http://localhost:4000",
|
|
468
|
+
agentId: "my-agent",
|
|
469
|
+
});
|
|
470
|
+
const a2uiRenderer = createA2UIMessageRenderer({
|
|
471
|
+
theme: {} as Theme,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
renderWithCopilotKit({
|
|
476
|
+
agent,
|
|
477
|
+
threadId,
|
|
478
|
+
renderActivityMessages: [a2uiRenderer],
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await waitFor(() => {
|
|
482
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
await waitFor(() => {
|
|
486
|
+
expect(mockPhoenixSockets).toHaveLength(1);
|
|
487
|
+
expect(mockPhoenixSockets[0]?.channels).toHaveLength(1);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const channel = mockPhoenixSockets[0]!.channels[0]!;
|
|
491
|
+
expect(channel.topic).toBe(`thread:${threadId}`);
|
|
492
|
+
expect(channel.params).toEqual({
|
|
493
|
+
stream_mode: "connect",
|
|
494
|
+
last_seen_event_id: null,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
channel.triggerJoin("ok");
|
|
498
|
+
channel.serverPush("ag_ui_event", {
|
|
499
|
+
type: EventType.RUN_STARTED,
|
|
500
|
+
threadId,
|
|
501
|
+
run_id: "backend-run-1",
|
|
502
|
+
input: {
|
|
503
|
+
messages: [
|
|
504
|
+
{
|
|
505
|
+
id: testId("connect-user-message"),
|
|
506
|
+
role: "user",
|
|
507
|
+
content: "show me the restored ui",
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
channel.serverPush("ag_ui_event", {
|
|
513
|
+
type: EventType.ACTIVITY_SNAPSHOT,
|
|
514
|
+
messageId: testId("connect-a2ui-activity"),
|
|
515
|
+
activityType: "a2ui-surface",
|
|
516
|
+
content: {
|
|
517
|
+
a2ui_operations: [
|
|
518
|
+
{
|
|
519
|
+
version: "v0.9",
|
|
520
|
+
createSurface: {
|
|
521
|
+
surfaceId,
|
|
522
|
+
catalogId:
|
|
523
|
+
"https://a2ui.org/specification/v0_9/basic_catalog.json",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
version: "v0.9",
|
|
528
|
+
updateComponents: {
|
|
529
|
+
surfaceId,
|
|
530
|
+
components: [
|
|
531
|
+
{
|
|
532
|
+
id: "root",
|
|
533
|
+
component: "Text",
|
|
534
|
+
text: "Restored dashboard",
|
|
535
|
+
variant: "body",
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
channel.serverPush("ag_ui_event", {
|
|
544
|
+
type: EventType.RUN_FINISHED,
|
|
545
|
+
});
|
|
546
|
+
channel.serverPush("stream_idle", { latestEventId: "event-3" });
|
|
547
|
+
|
|
548
|
+
await waitFor(() => {
|
|
549
|
+
expect(screen.getByText("show me the restored ui")).toBeDefined();
|
|
550
|
+
});
|
|
551
|
+
await waitFor(() => {
|
|
552
|
+
expect(
|
|
553
|
+
document.querySelector(`[data-surface-id='${surfaceId}']`),
|
|
554
|
+
).not.toBeNull();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit];
|
|
558
|
+
expect(url).toContain("/agent/my-agent/connect");
|
|
559
|
+
expect(options.method).toBe("POST");
|
|
560
|
+
|
|
561
|
+
const requestBody = JSON.parse(String(options.body)) as {
|
|
562
|
+
threadId: string;
|
|
563
|
+
lastSeenEventId: string | null;
|
|
564
|
+
messages: unknown[];
|
|
565
|
+
};
|
|
566
|
+
expect(requestBody.threadId).toBe(threadId);
|
|
567
|
+
expect(requestBody.lastSeenEventId).toBeNull();
|
|
568
|
+
expect(requestBody.messages).toEqual([]);
|
|
569
|
+
} finally {
|
|
570
|
+
vi.unstubAllGlobals();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("restores a completed Open Generative UI activity after reconnect from an event-native baseline", async () => {
|
|
575
|
+
mockWebsandboxCreate.mockClear();
|
|
576
|
+
mockWebsandboxDestroy.mockClear();
|
|
577
|
+
|
|
578
|
+
const agent = new MockReconnectableAgent();
|
|
579
|
+
const threadId = testId("open-generative-ui-thread");
|
|
580
|
+
const restoredHtml =
|
|
581
|
+
"<head></head><body><div>Restored open generative UI</div></body>";
|
|
582
|
+
|
|
583
|
+
const renderOpenGenerativeUIChat = () =>
|
|
584
|
+
render(
|
|
585
|
+
<CopilotKitProvider
|
|
586
|
+
agents__unsafe_dev_only={{ default: agent }}
|
|
587
|
+
openGenerativeUI={{}}
|
|
588
|
+
>
|
|
589
|
+
<CopilotChatConfigurationProvider threadId={threadId}>
|
|
590
|
+
<div style={{ height: 400 }}>
|
|
591
|
+
<CopilotChat welcomeScreen={false} />
|
|
592
|
+
</div>
|
|
593
|
+
</CopilotChatConfigurationProvider>
|
|
594
|
+
</CopilotKitProvider>,
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const { unmount } = renderOpenGenerativeUIChat();
|
|
598
|
+
|
|
599
|
+
const input = await screen.findByRole("textbox");
|
|
600
|
+
fireEvent.change(input, { target: { value: "Show me the restored app" } });
|
|
601
|
+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
602
|
+
|
|
603
|
+
await waitFor(() => {
|
|
604
|
+
expect(screen.getByText("Show me the restored app")).toBeDefined();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
agent.emit(runStartedEvent());
|
|
608
|
+
agent.emit(
|
|
609
|
+
activitySnapshotEvent({
|
|
610
|
+
messageId: testId("open-generative-ui-activity"),
|
|
611
|
+
activityType: "open-generative-ui",
|
|
612
|
+
content: {
|
|
613
|
+
initialHeight: 180,
|
|
614
|
+
generating: false,
|
|
615
|
+
html: [restoredHtml],
|
|
616
|
+
htmlComplete: true,
|
|
617
|
+
},
|
|
618
|
+
}),
|
|
619
|
+
);
|
|
620
|
+
agent.emit(runFinishedEvent());
|
|
621
|
+
agent.complete();
|
|
622
|
+
|
|
623
|
+
await waitFor(() => {
|
|
624
|
+
expect(mockWebsandboxCreate).toHaveBeenCalledTimes(1);
|
|
625
|
+
});
|
|
626
|
+
expect(mockWebsandboxCreate.mock.calls[0]?.[1]).toMatchObject({
|
|
627
|
+
frameContent: restoredHtml,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
unmount();
|
|
631
|
+
|
|
632
|
+
agent.reset();
|
|
633
|
+
|
|
634
|
+
renderOpenGenerativeUIChat();
|
|
635
|
+
|
|
636
|
+
await waitFor(() => {
|
|
637
|
+
expect(mockWebsandboxCreate).toHaveBeenCalledTimes(2);
|
|
638
|
+
});
|
|
639
|
+
expect(mockWebsandboxCreate.mock.calls[1]?.[1]).toMatchObject({
|
|
640
|
+
frameContent: restoredHtml,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
expect(mockWebsandboxDestroy).toHaveBeenCalledTimes(1);
|
|
644
|
+
});
|
|
211
645
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { CopilotChatView } from "../CopilotChatView";
|
|
5
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
6
|
+
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
7
|
+
|
|
8
|
+
// Minimal provider wrapper. No agent registry is required because these tests
|
|
9
|
+
// only exercise local render decisions (welcome-screen suppression) that
|
|
10
|
+
// don't touch the agent runtime.
|
|
11
|
+
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
12
|
+
<CopilotKitProvider>
|
|
13
|
+
<CopilotChatConfigurationProvider threadId="test-thread">
|
|
14
|
+
<div style={{ height: 400 }}>{children}</div>
|
|
15
|
+
</CopilotChatConfigurationProvider>
|
|
16
|
+
</CopilotKitProvider>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe("CopilotChatView connect-gating", () => {
|
|
20
|
+
it("suppresses the welcome screen while isConnecting=true", () => {
|
|
21
|
+
render(
|
|
22
|
+
<TestWrapper>
|
|
23
|
+
<CopilotChatView messages={[]} isConnecting />
|
|
24
|
+
</TestWrapper>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Switching threads would otherwise flash the welcome greeting before
|
|
28
|
+
// bootstrap messages arrive.
|
|
29
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("suppresses the welcome screen when hasExplicitThreadId=true", () => {
|
|
33
|
+
render(
|
|
34
|
+
<TestWrapper>
|
|
35
|
+
<CopilotChatView messages={[]} hasExplicitThreadId />
|
|
36
|
+
</TestWrapper>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// A caller-managed thread (threadId prop / config provider) should never
|
|
40
|
+
// display the generic "start a new chat" welcome — even when the thread
|
|
41
|
+
// has no messages yet.
|
|
42
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("shows the welcome screen by default for a fresh empty chat", () => {
|
|
46
|
+
render(
|
|
47
|
+
<TestWrapper>
|
|
48
|
+
<CopilotChatView messages={[]} />
|
|
49
|
+
</TestWrapper>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Positive control: with no threadId supplied and no connect in flight,
|
|
53
|
+
// an empty chat should still render the welcome screen.
|
|
54
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|