@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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { describe, it, expect, vi } from "vitest";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { EMPTY, Observable } from "rxjs";
|
|
5
|
+
import { type BaseEvent, type RunAgentInput } from "@ag-ui/client";
|
|
6
|
+
import { MockStepwiseAgent } from "../../../__tests__/utils/test-helpers";
|
|
7
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
8
|
+
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
9
|
+
import { CopilotChat } from "../CopilotChat";
|
|
10
|
+
|
|
11
|
+
describe("CopilotChat avoids /connect for locally-generated threadIds (ENT-314)", () => {
|
|
12
|
+
function buildAgentWithConnectSpy(): {
|
|
13
|
+
agent: MockStepwiseAgent;
|
|
14
|
+
connectSpy: ReturnType<typeof vi.fn>;
|
|
15
|
+
} {
|
|
16
|
+
const connectSpy = vi.fn();
|
|
17
|
+
class SpyAgent extends MockStepwiseAgent {
|
|
18
|
+
connect(input: RunAgentInput): Observable<BaseEvent> {
|
|
19
|
+
connectSpy(input);
|
|
20
|
+
return EMPTY;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { agent: new SpyAgent(), connectSpy };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
it("does not call connect() when no threadId is supplied", async () => {
|
|
27
|
+
const { agent, connectSpy } = buildAgentWithConnectSpy();
|
|
28
|
+
|
|
29
|
+
render(
|
|
30
|
+
<CopilotKitProvider agents__unsafe_dev_only={{ default: agent }}>
|
|
31
|
+
<CopilotChat welcomeScreen={false} />
|
|
32
|
+
</CopilotKitProvider>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
36
|
+
expect(connectSpy).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("calls connect() when a threadId is supplied via props", async () => {
|
|
40
|
+
const { agent, connectSpy } = buildAgentWithConnectSpy();
|
|
41
|
+
|
|
42
|
+
render(
|
|
43
|
+
<CopilotKitProvider agents__unsafe_dev_only={{ default: agent }}>
|
|
44
|
+
<CopilotChat welcomeScreen={false} threadId="user-thread-abc" />
|
|
45
|
+
</CopilotKitProvider>,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
49
|
+
expect(connectSpy).toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("calls connect() when a threadId is supplied via configuration provider", async () => {
|
|
53
|
+
const { agent, connectSpy } = buildAgentWithConnectSpy();
|
|
54
|
+
|
|
55
|
+
render(
|
|
56
|
+
<CopilotKitProvider agents__unsafe_dev_only={{ default: agent }}>
|
|
57
|
+
<CopilotChatConfigurationProvider threadId="config-thread-xyz">
|
|
58
|
+
<CopilotChat welcomeScreen={false} />
|
|
59
|
+
</CopilotChatConfigurationProvider>
|
|
60
|
+
</CopilotKitProvider>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
64
|
+
expect(connectSpy).toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import { beforeEach, vi } from "vitest";
|
|
4
|
+
import { useConfigureSuggestions } from "../../../hooks/use-configure-suggestions";
|
|
5
|
+
import { CopilotChat } from "../CopilotChat";
|
|
6
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
7
|
+
import {
|
|
8
|
+
MockStepwiseAgent,
|
|
9
|
+
runStartedEvent,
|
|
10
|
+
runFinishedEvent,
|
|
11
|
+
textChunkEvent,
|
|
12
|
+
testId,
|
|
13
|
+
} from "../../../__tests__/utils/test-helpers";
|
|
14
|
+
import type { AutoScrollMode } from "../normalize-auto-scroll";
|
|
15
|
+
|
|
16
|
+
// jsdom doesn't implement scrollTo; pin-to-send mode calls it from a rAF
|
|
17
|
+
// callback, so without this stub the cleanup throws an unhandled error.
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
HTMLElement.prototype.scrollTo = vi.fn();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const STATIC_SUGGESTIONS = [
|
|
23
|
+
{ title: "Say hello", message: "Hello there!" },
|
|
24
|
+
{ title: "Get help", message: "Can you help me?" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const ChatWithStaticAlwaysSuggestions: React.FC<{
|
|
28
|
+
autoScroll?: AutoScrollMode | boolean;
|
|
29
|
+
consumerAgentId?: string;
|
|
30
|
+
}> = ({ autoScroll, consumerAgentId }) => {
|
|
31
|
+
useConfigureSuggestions({
|
|
32
|
+
suggestions: STATIC_SUGGESTIONS,
|
|
33
|
+
available: "always",
|
|
34
|
+
...(consumerAgentId ? { consumerAgentId } : {}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return <CopilotChat autoScroll={autoScroll} />;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function renderChat({
|
|
41
|
+
agent,
|
|
42
|
+
autoScroll,
|
|
43
|
+
consumerAgentId,
|
|
44
|
+
}: {
|
|
45
|
+
agent: MockStepwiseAgent;
|
|
46
|
+
autoScroll?: AutoScrollMode | boolean;
|
|
47
|
+
consumerAgentId?: string;
|
|
48
|
+
}) {
|
|
49
|
+
return render(
|
|
50
|
+
<CopilotKitProvider agents__unsafe_dev_only={{ default: agent }}>
|
|
51
|
+
<div style={{ height: 400 }}>
|
|
52
|
+
<ChatWithStaticAlwaysSuggestions
|
|
53
|
+
autoScroll={autoScroll}
|
|
54
|
+
consumerAgentId={consumerAgentId}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</CopilotKitProvider>,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("CopilotChat - static suggestions with available:'always'", () => {
|
|
62
|
+
it("should show suggestions on the welcome screen", async () => {
|
|
63
|
+
const agent = new MockStepwiseAgent();
|
|
64
|
+
renderChat({ agent, consumerAgentId: "default" });
|
|
65
|
+
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await waitFor(() => {
|
|
71
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
72
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should show suggestions on the welcome screen with global config (no consumerAgentId)", async () => {
|
|
77
|
+
const agent = new MockStepwiseAgent();
|
|
78
|
+
renderChat({ agent });
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await waitFor(() => {
|
|
85
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
86
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should keep suggestions visible during a run", async () => {
|
|
91
|
+
const agent = new MockStepwiseAgent();
|
|
92
|
+
renderChat({ agent, consumerAgentId: "default" });
|
|
93
|
+
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const input = await screen.findByRole("textbox");
|
|
103
|
+
fireEvent.change(input, { target: { value: "Hi!" } });
|
|
104
|
+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(screen.getByText("Hi!")).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const messageId = testId("msg");
|
|
111
|
+
agent.emit(runStartedEvent());
|
|
112
|
+
agent.emit(textChunkEvent(messageId, "Hello! How can I help?"));
|
|
113
|
+
|
|
114
|
+
// Suggestions must remain visible even while the run is still in progress
|
|
115
|
+
await waitFor(
|
|
116
|
+
() => {
|
|
117
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
118
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
119
|
+
},
|
|
120
|
+
{ timeout: 3000 },
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
agent.emit(runFinishedEvent());
|
|
124
|
+
agent.complete();
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(screen.getByText("Hello! How can I help?")).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// And still visible after the run completes
|
|
131
|
+
await waitFor(
|
|
132
|
+
() => {
|
|
133
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
134
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
135
|
+
},
|
|
136
|
+
{ timeout: 3000 },
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should keep suggestions visible during a run in pin-to-send mode", async () => {
|
|
141
|
+
const agent = new MockStepwiseAgent();
|
|
142
|
+
renderChat({ agent, autoScroll: "pin-to-send" });
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const input = await screen.findByRole("textbox");
|
|
153
|
+
fireEvent.change(input, { target: { value: "Hi!" } });
|
|
154
|
+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(screen.getByText("Hi!")).toBeDefined();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const messageId = testId("msg");
|
|
161
|
+
agent.emit(runStartedEvent());
|
|
162
|
+
agent.emit(textChunkEvent(messageId, "Hello! How can I help?"));
|
|
163
|
+
|
|
164
|
+
// Suggestions must remain visible even while the run is still in progress
|
|
165
|
+
await waitFor(
|
|
166
|
+
() => {
|
|
167
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
168
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
169
|
+
},
|
|
170
|
+
{ timeout: 3000 },
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
agent.emit(runFinishedEvent());
|
|
174
|
+
agent.complete();
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(screen.getByText("Hello! How can I help?")).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// And still visible after the run completes
|
|
181
|
+
await waitFor(
|
|
182
|
+
() => {
|
|
183
|
+
expect(screen.getByText("Say hello")).toBeDefined();
|
|
184
|
+
expect(screen.getByText("Get help")).toBeDefined();
|
|
185
|
+
},
|
|
186
|
+
{ timeout: 3000 },
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
4
|
+
import { CopilotKitProvider } from "../../../providers/CopilotKitProvider";
|
|
5
|
+
import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider";
|
|
6
|
+
import { CopilotChat } from "../CopilotChat";
|
|
7
|
+
import { MockStepwiseAgent } from "../../../__tests__/utils/test-helpers";
|
|
8
|
+
import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mock agent that records every connectAgent() invocation and resolves
|
|
12
|
+
* immediately with an empty run result. Tracking lives on the class so
|
|
13
|
+
* per-thread clones (from useAgent's WeakMap) share the counter.
|
|
14
|
+
*/
|
|
15
|
+
class TrackingAgent extends MockStepwiseAgent {
|
|
16
|
+
static connectCalls: Array<{
|
|
17
|
+
threadId: string | undefined;
|
|
18
|
+
agentId: string | undefined;
|
|
19
|
+
}> = [];
|
|
20
|
+
|
|
21
|
+
static reset() {
|
|
22
|
+
TrackingAgent.connectCalls = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connectAgent(
|
|
26
|
+
_params: unknown,
|
|
27
|
+
_subscriber: unknown,
|
|
28
|
+
): Promise<{ result: unknown; newMessages: [] }> {
|
|
29
|
+
TrackingAgent.connectCalls.push({
|
|
30
|
+
threadId: this.threadId,
|
|
31
|
+
agentId: this.agentId,
|
|
32
|
+
});
|
|
33
|
+
return { result: undefined, newMessages: [] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderWithKit(ui: React.ReactNode, agent: TrackingAgent) {
|
|
38
|
+
return render(
|
|
39
|
+
<CopilotKitProvider agents__unsafe_dev_only={{ [DEFAULT_AGENT_ID]: agent }}>
|
|
40
|
+
<div style={{ height: 400 }}>{ui}</div>
|
|
41
|
+
</CopilotKitProvider>,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Regression coverage for fix/welcome-not-showing-at-all.
|
|
47
|
+
*
|
|
48
|
+
* The underlying bug: the v1 <CopilotKit> wrapper pipes a ThreadsProvider-
|
|
49
|
+
* minted UUID through to CopilotChatConfigurationProvider as `threadId`.
|
|
50
|
+
* CopilotChat previously treated any non-empty providedThreadId as "caller
|
|
51
|
+
* supplied a real backend thread" and (a) fired /connect (→ 404 for an
|
|
52
|
+
* auto-minted UUID) and (b) suppressed the welcome screen forever. The
|
|
53
|
+
* fix threads an `hasExplicitThreadId` signal through the provider chain;
|
|
54
|
+
* these tests pin the contract that /connect and welcome-screen gating
|
|
55
|
+
* now follow that signal rather than `!!threadId`.
|
|
56
|
+
*/
|
|
57
|
+
describe("CopilotChat welcome / connect integration", () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
TrackingAgent.reset();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("v1 bridge scenario (config provider marks threadId as non-explicit)", () => {
|
|
63
|
+
it("does not call connectAgent and shows the welcome screen", async () => {
|
|
64
|
+
const agent = new TrackingAgent();
|
|
65
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
66
|
+
|
|
67
|
+
renderWithKit(
|
|
68
|
+
<CopilotChatConfigurationProvider
|
|
69
|
+
threadId="auto-minted-uuid"
|
|
70
|
+
hasExplicitThreadId={false}
|
|
71
|
+
>
|
|
72
|
+
<CopilotChat />
|
|
73
|
+
</CopilotChatConfigurationProvider>,
|
|
74
|
+
agent,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Give the connect-effect a chance to misfire.
|
|
78
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
79
|
+
|
|
80
|
+
expect(TrackingAgent.connectCalls).toHaveLength(0);
|
|
81
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("plain CopilotChat (no threadId anywhere)", () => {
|
|
86
|
+
it("does not call connectAgent and shows the welcome screen", async () => {
|
|
87
|
+
const agent = new TrackingAgent();
|
|
88
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
89
|
+
|
|
90
|
+
renderWithKit(<CopilotChat />, agent);
|
|
91
|
+
|
|
92
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
93
|
+
|
|
94
|
+
expect(TrackingAgent.connectCalls).toHaveLength(0);
|
|
95
|
+
expect(screen.getByTestId("copilot-welcome-screen")).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("explicit threadId via CopilotChat prop", () => {
|
|
100
|
+
it("calls connectAgent with that threadId and suppresses the welcome screen", async () => {
|
|
101
|
+
const agent = new TrackingAgent();
|
|
102
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
103
|
+
|
|
104
|
+
renderWithKit(<CopilotChat threadId="real-thread" />, agent);
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(TrackingAgent.connectCalls.length).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// The per-thread clone carries threadId; agentId is the default.
|
|
111
|
+
expect(
|
|
112
|
+
TrackingAgent.connectCalls.some((c) => c.threadId === "real-thread"),
|
|
113
|
+
).toBe(true);
|
|
114
|
+
|
|
115
|
+
// Welcome screen is suppressed even after connect resolves, because the
|
|
116
|
+
// thread was caller-picked (hasExplicitThreadId=true).
|
|
117
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("explicit threadId via wrapping CopilotChatConfigurationProvider", () => {
|
|
122
|
+
it("inherits explicitness from the provider and connects", async () => {
|
|
123
|
+
const agent = new TrackingAgent();
|
|
124
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
125
|
+
|
|
126
|
+
renderWithKit(
|
|
127
|
+
<CopilotChatConfigurationProvider threadId="from-config">
|
|
128
|
+
<CopilotChat />
|
|
129
|
+
</CopilotChatConfigurationProvider>,
|
|
130
|
+
agent,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(TrackingAgent.connectCalls.length).toBeGreaterThan(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
TrackingAgent.connectCalls.some((c) => c.threadId === "from-config"),
|
|
139
|
+
).toBe(true);
|
|
140
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("thread switch between two explicit threads", () => {
|
|
145
|
+
it("keeps the welcome screen hidden across the switch", async () => {
|
|
146
|
+
const agent = new TrackingAgent();
|
|
147
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
148
|
+
|
|
149
|
+
const { rerender } = renderWithKit(
|
|
150
|
+
<CopilotChat threadId="thread-a" />,
|
|
151
|
+
agent,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await waitFor(() => {
|
|
155
|
+
expect(
|
|
156
|
+
TrackingAgent.connectCalls.some((c) => c.threadId === "thread-a"),
|
|
157
|
+
).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
// After thread-a's connect resolves, welcome must still be hidden
|
|
160
|
+
// because the thread is caller-picked.
|
|
161
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
162
|
+
|
|
163
|
+
rerender(
|
|
164
|
+
<CopilotKitProvider
|
|
165
|
+
agents__unsafe_dev_only={{ [DEFAULT_AGENT_ID]: agent }}
|
|
166
|
+
>
|
|
167
|
+
<div style={{ height: 400 }}>
|
|
168
|
+
<CopilotChat threadId="thread-b" />
|
|
169
|
+
</div>
|
|
170
|
+
</CopilotKitProvider>,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// During the switch (lastConnected="thread-a" !== "thread-b") isConnecting
|
|
174
|
+
// is true — welcome must not flash.
|
|
175
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
176
|
+
|
|
177
|
+
await waitFor(() => {
|
|
178
|
+
expect(
|
|
179
|
+
TrackingAgent.connectCalls.some((c) => c.threadId === "thread-b"),
|
|
180
|
+
).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
// And after thread-b's connect resolves.
|
|
183
|
+
expect(screen.queryByTestId("copilot-welcome-screen")).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|