@stigmer/react 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/__tests__/useAgent.test.d.ts +2 -0
- package/agent/__tests__/useAgent.test.d.ts.map +1 -0
- package/agent/__tests__/useAgent.test.js +86 -0
- package/agent/__tests__/useAgent.test.js.map +1 -0
- package/agent/__tests__/useDefaultAgent.test.js +106 -223
- package/agent/__tests__/useDefaultAgent.test.js.map +1 -1
- package/agent/useDefaultAgent.d.ts +9 -0
- package/agent/useDefaultAgent.d.ts.map +1 -1
- package/agent/useDefaultAgent.js +35 -5
- package/agent/useDefaultAgent.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +18 -16
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +10 -11
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/ConfigureMenu.d.ts.map +1 -1
- package/composer/ConfigureMenu.js +1 -1
- package/composer/ConfigureMenu.js.map +1 -1
- package/composer/ContextPopover.d.ts +1 -3
- package/composer/ContextPopover.d.ts.map +1 -1
- package/composer/ContextPopover.js +2 -2
- package/composer/ContextPopover.js.map +1 -1
- package/composer/SessionComposer.js +5 -5
- package/composer/SessionComposer.js.map +1 -1
- package/composer/icons.js +3 -3
- package/internal/withTimeout.d.ts +8 -0
- package/internal/withTimeout.d.ts.map +1 -0
- package/internal/withTimeout.js +19 -0
- package/internal/withTimeout.js.map +1 -0
- package/package.json +4 -4
- package/session/__tests__/useCreateSession.test.js +99 -191
- package/session/__tests__/useCreateSession.test.js.map +1 -1
- package/session/__tests__/useNewSessionFlow.test.js +71 -0
- package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
- package/session/__tests__/useSession.test.js +71 -108
- package/session/__tests__/useSession.test.js.map +1 -1
- package/session/__tests__/useSessionList.test.d.ts +2 -0
- package/session/__tests__/useSessionList.test.d.ts.map +1 -0
- package/session/__tests__/useSessionList.test.js +63 -0
- package/session/__tests__/useSessionList.test.js.map +1 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +13 -7
- package/session/useNewSessionFlow.js.map +1 -1
- package/src/agent/__tests__/useAgent.test.tsx +116 -0
- package/src/agent/__tests__/useDefaultAgent.test.tsx +115 -240
- package/src/agent/useDefaultAgent.ts +53 -2
- package/src/composer/ComposerToolbar.tsx +76 -96
- package/src/composer/ConfigureMenu.tsx +16 -14
- package/src/composer/ContextPopover.tsx +11 -11
- package/src/composer/SessionComposer.tsx +6 -6
- package/src/composer/icons.tsx +6 -6
- package/src/internal/withTimeout.ts +25 -0
- package/src/session/__tests__/useCreateSession.test.tsx +114 -235
- package/src/session/__tests__/useNewSessionFlow.test.tsx +96 -1
- package/src/session/__tests__/useSession.test.tsx +82 -141
- package/src/session/__tests__/useSessionList.test.tsx +86 -0
- package/src/session/useNewSessionFlow.ts +18 -9
- package/styles.css +1 -1
|
@@ -1,296 +1,175 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach
|
|
2
|
-
import { renderHook, act } from "@testing-library/react";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import { Harness } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
5
|
-
import type { Stigmer } from "@stigmer/sdk";
|
|
6
4
|
import { StigmerContext } from "../../context";
|
|
5
|
+
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
7
6
|
import { useCreateSession } from "../useCreateSession";
|
|
8
7
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
function createMockStigmer(overrides: {
|
|
9
|
+
getByReference?: (...args: unknown[]) => Promise<unknown>;
|
|
10
|
+
create?: (...args: unknown[]) => Promise<unknown>;
|
|
12
11
|
} = {}) {
|
|
13
12
|
return {
|
|
14
|
-
session: {
|
|
15
|
-
create: overrides.sessionCreate ?? vi.fn(),
|
|
16
|
-
},
|
|
17
13
|
agent: {
|
|
18
|
-
getByReference: overrides.
|
|
14
|
+
getByReference: overrides.getByReference ?? vi.fn().mockResolvedValue({
|
|
15
|
+
status: { defaultInstanceId: "ain-default" },
|
|
16
|
+
}),
|
|
19
17
|
},
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
);
|
|
18
|
+
session: {
|
|
19
|
+
create: overrides.create ?? vi.fn().mockResolvedValue({
|
|
20
|
+
metadata: { id: "ses-new-1" },
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
} as never;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
function
|
|
30
|
-
return {
|
|
26
|
+
function wrapper(client: unknown) {
|
|
27
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
28
|
+
return (
|
|
29
|
+
<FetchCacheContext.Provider value={null}>
|
|
30
|
+
<StigmerContext.Provider value={client as never}>
|
|
31
|
+
{children}
|
|
32
|
+
</StigmerContext.Provider>
|
|
33
|
+
</FetchCacheContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
describe("useCreateSession", () => {
|
|
34
|
-
let sessionCreateMock: ReturnType<typeof vi.fn>;
|
|
35
|
-
let agentGetByRefMock: ReturnType<typeof vi.fn>;
|
|
36
|
-
let client: Stigmer;
|
|
37
|
-
|
|
38
39
|
beforeEach(() => {
|
|
39
|
-
sessionCreateMock = vi.fn();
|
|
40
|
-
agentGetByRefMock = vi.fn();
|
|
41
|
-
client = buildMockClient({
|
|
42
|
-
sessionCreate: sessionCreateMock,
|
|
43
|
-
agentGetByReference: agentGetByRefMock,
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
40
|
vi.restoreAllMocks();
|
|
49
41
|
});
|
|
50
42
|
|
|
51
|
-
it("
|
|
43
|
+
it("creates a session with agentInstanceId", async () => {
|
|
44
|
+
const create = vi.fn().mockResolvedValue({ metadata: { id: "ses-123" } });
|
|
45
|
+
const client = createMockStigmer({ create });
|
|
46
|
+
|
|
52
47
|
const { result } = renderHook(() => useCreateSession(), {
|
|
53
|
-
wrapper:
|
|
48
|
+
wrapper: wrapper(client),
|
|
54
49
|
});
|
|
55
|
-
expect(result.current.isCreating).toBe(false);
|
|
56
|
-
expect(result.current.error).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("agentInstanceId path", () => {
|
|
60
|
-
it("creates a session with the given instance ID", async () => {
|
|
61
|
-
sessionCreateMock.mockResolvedValueOnce(fakeSessionResponse("sess-1"));
|
|
62
|
-
|
|
63
|
-
const { result } = renderHook(() => useCreateSession(), {
|
|
64
|
-
wrapper: makeWrapper(client),
|
|
65
|
-
});
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
await act(async () => {
|
|
69
|
-
outcome = await result.current.create({
|
|
70
|
-
org: "acme",
|
|
71
|
-
agentInstanceId: "inst-abc",
|
|
72
|
-
});
|
|
73
|
-
});
|
|
51
|
+
expect(result.current.isCreating).toBe(false);
|
|
74
52
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
53
|
+
let sessionResult: { sessionId: string } | undefined;
|
|
54
|
+
await act(async () => {
|
|
55
|
+
sessionResult = await result.current.create({
|
|
78
56
|
org: "acme",
|
|
79
|
-
agentInstanceId: "
|
|
57
|
+
agentInstanceId: "ain-123",
|
|
80
58
|
});
|
|
81
59
|
});
|
|
82
|
-
});
|
|
83
60
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
expect(sessionResult!.sessionId).toBe("ses-123");
|
|
62
|
+
expect(result.current.isCreating).toBe(false);
|
|
63
|
+
expect(result.current.error).toBeNull();
|
|
64
|
+
expect(create).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(create).toHaveBeenCalledWith(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
org: "acme",
|
|
68
|
+
agentInstanceId: "ain-123",
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
90
72
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
73
|
+
it("resolves agentRef to default instance before creating", async () => {
|
|
74
|
+
const getByReference = vi.fn().mockResolvedValue({
|
|
75
|
+
status: { defaultInstanceId: "ain-resolved" },
|
|
76
|
+
});
|
|
77
|
+
const create = vi.fn().mockResolvedValue({ metadata: { id: "ses-456" } });
|
|
78
|
+
const client = createMockStigmer({ getByReference, create });
|
|
94
79
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
agentRef: { org: "acme", slug: "my-agent" },
|
|
99
|
-
});
|
|
100
|
-
});
|
|
80
|
+
const { result } = renderHook(() => useCreateSession(), {
|
|
81
|
+
wrapper: wrapper(client),
|
|
82
|
+
});
|
|
101
83
|
|
|
102
|
-
|
|
84
|
+
await act(async () => {
|
|
85
|
+
await result.current.create({
|
|
103
86
|
org: "acme",
|
|
104
|
-
slug: "my-agent",
|
|
105
|
-
});
|
|
106
|
-
expect(sessionCreateMock.mock.calls[0][0]).toMatchObject({
|
|
107
|
-
agentInstanceId: "inst-resolved",
|
|
87
|
+
agentRef: { org: "acme", slug: "my-agent" },
|
|
108
88
|
});
|
|
109
89
|
});
|
|
110
90
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
wrapper: makeWrapper(client),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
await act(async () => {
|
|
119
|
-
await expect(
|
|
120
|
-
result.current.create({
|
|
121
|
-
org: "acme",
|
|
122
|
-
agentRef: { org: "acme", slug: "no-instance" },
|
|
123
|
-
}),
|
|
124
|
-
).rejects.toThrow("does not have a default instance");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
expect(result.current.error).not.toBeNull();
|
|
128
|
-
expect(result.current.error!.message).toContain(
|
|
129
|
-
"does not have a default instance",
|
|
130
|
-
);
|
|
131
|
-
});
|
|
91
|
+
expect(getByReference).toHaveBeenCalledWith({ org: "acme", slug: "my-agent" });
|
|
92
|
+
expect(create).toHaveBeenCalledWith(
|
|
93
|
+
expect.objectContaining({ agentInstanceId: "ain-resolved" }),
|
|
94
|
+
);
|
|
132
95
|
});
|
|
133
96
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
97
|
+
it("errors when agentRef resolves to agent without default instance", async () => {
|
|
98
|
+
const getByReference = vi.fn().mockResolvedValue({
|
|
99
|
+
status: { defaultInstanceId: "" },
|
|
100
|
+
});
|
|
101
|
+
const client = createMockStigmer({ getByReference });
|
|
137
102
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
103
|
+
const { result } = renderHook(() => useCreateSession(), {
|
|
104
|
+
wrapper: wrapper(client),
|
|
105
|
+
});
|
|
141
106
|
|
|
142
|
-
|
|
107
|
+
// The hook catches the error, sets state, and re-throws.
|
|
108
|
+
// We need to catch the rethrow to inspect the error state.
|
|
109
|
+
let caughtError: Error | undefined;
|
|
110
|
+
await act(async () => {
|
|
111
|
+
try {
|
|
143
112
|
await result.current.create({
|
|
144
113
|
org: "acme",
|
|
145
|
-
|
|
146
|
-
harness: "cursor",
|
|
114
|
+
agentRef: { org: "acme", slug: "no-instance-agent" },
|
|
147
115
|
});
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
|
|
116
|
+
} catch (err) {
|
|
117
|
+
caughtError = err as Error;
|
|
118
|
+
}
|
|
151
119
|
});
|
|
152
120
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
});
|
|
121
|
+
expect(caughtError).toBeTruthy();
|
|
122
|
+
expect(caughtError!.message).toContain("does not have a default instance");
|
|
123
|
+
expect(result.current.error).toBeTruthy();
|
|
124
|
+
expect(result.current.error!.message).toContain("does not have a default instance");
|
|
125
|
+
});
|
|
159
126
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
agentInstanceId: "inst-1",
|
|
164
|
-
harness: "native",
|
|
165
|
-
});
|
|
166
|
-
});
|
|
127
|
+
it("sets error state on session.create failure", async () => {
|
|
128
|
+
const create = vi.fn().mockRejectedValue(new Error("Permission denied"));
|
|
129
|
+
const client = createMockStigmer({ create });
|
|
167
130
|
|
|
168
|
-
|
|
131
|
+
const { result } = renderHook(() => useCreateSession(), {
|
|
132
|
+
wrapper: wrapper(client),
|
|
169
133
|
});
|
|
170
134
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const { result } = renderHook(() => useCreateSession(), {
|
|
175
|
-
wrapper: makeWrapper(client),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await act(async () => {
|
|
135
|
+
let caughtError: Error | undefined;
|
|
136
|
+
await act(async () => {
|
|
137
|
+
try {
|
|
179
138
|
await result.current.create({
|
|
180
139
|
org: "acme",
|
|
181
|
-
agentInstanceId: "
|
|
140
|
+
agentInstanceId: "ain-123",
|
|
182
141
|
});
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
|
|
142
|
+
} catch (err) {
|
|
143
|
+
caughtError = err as Error;
|
|
144
|
+
}
|
|
186
145
|
});
|
|
187
|
-
});
|
|
188
146
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
new Promise((r) => { resolveCreate = r; }),
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const { result } = renderHook(() => useCreateSession(), {
|
|
197
|
-
wrapper: makeWrapper(client),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
let createPromise: Promise<unknown>;
|
|
201
|
-
act(() => {
|
|
202
|
-
createPromise = result.current.create({
|
|
203
|
-
org: "acme",
|
|
204
|
-
agentInstanceId: "inst-1",
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
expect(result.current.isCreating).toBe(true);
|
|
209
|
-
|
|
210
|
-
await act(async () => {
|
|
211
|
-
resolveCreate(fakeSessionResponse("sess-lc"));
|
|
212
|
-
await createPromise;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
expect(result.current.isCreating).toBe(false);
|
|
216
|
-
});
|
|
147
|
+
expect(caughtError).toBeTruthy();
|
|
148
|
+
expect(caughtError!.message).toBe("Permission denied");
|
|
149
|
+
expect(result.current.error!.message).toBe("Permission denied");
|
|
150
|
+
expect(result.current.isCreating).toBe(false);
|
|
217
151
|
});
|
|
218
152
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
153
|
+
it("clearError resets the error state", async () => {
|
|
154
|
+
const create = vi.fn().mockRejectedValue(new Error("fail"));
|
|
155
|
+
const client = createMockStigmer({ create });
|
|
222
156
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
await act(async () => {
|
|
228
|
-
await expect(
|
|
229
|
-
result.current.create({
|
|
230
|
-
org: "acme",
|
|
231
|
-
agentInstanceId: "inst-1",
|
|
232
|
-
}),
|
|
233
|
-
).rejects.toThrow("network timeout");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
expect(result.current.isCreating).toBe(false);
|
|
237
|
-
expect(result.current.error).toBeInstanceOf(Error);
|
|
238
|
-
expect(result.current.error!.message).toBe("network timeout");
|
|
157
|
+
const { result } = renderHook(() => useCreateSession(), {
|
|
158
|
+
wrapper: wrapper(client),
|
|
239
159
|
});
|
|
240
160
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
await act(async () => {
|
|
249
|
-
try {
|
|
250
|
-
await result.current.create({
|
|
251
|
-
org: "acme",
|
|
252
|
-
agentInstanceId: "inst-1",
|
|
253
|
-
});
|
|
254
|
-
} catch { /* expected */ }
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(result.current.error).not.toBeNull();
|
|
258
|
-
|
|
259
|
-
act(() => {
|
|
260
|
-
result.current.clearError();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
expect(result.current.error).toBeNull();
|
|
161
|
+
await act(async () => {
|
|
162
|
+
try {
|
|
163
|
+
await result.current.create({ org: "acme", agentInstanceId: "ain-1" });
|
|
164
|
+
} catch {
|
|
165
|
+
// expected
|
|
166
|
+
}
|
|
264
167
|
});
|
|
265
168
|
|
|
266
|
-
|
|
267
|
-
sessionCreateMock
|
|
268
|
-
.mockRejectedValueOnce(new Error("first fail"))
|
|
269
|
-
.mockResolvedValueOnce(fakeSessionResponse("sess-retry"));
|
|
270
|
-
|
|
271
|
-
const { result } = renderHook(() => useCreateSession(), {
|
|
272
|
-
wrapper: makeWrapper(client),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await act(async () => {
|
|
276
|
-
try {
|
|
277
|
-
await result.current.create({
|
|
278
|
-
org: "acme",
|
|
279
|
-
agentInstanceId: "inst-1",
|
|
280
|
-
});
|
|
281
|
-
} catch { /* expected */ }
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
expect(result.current.error).not.toBeNull();
|
|
169
|
+
expect(result.current.error).toBeTruthy();
|
|
285
170
|
|
|
286
|
-
|
|
287
|
-
await result.current.create({
|
|
288
|
-
org: "acme",
|
|
289
|
-
agentInstanceId: "inst-1",
|
|
290
|
-
});
|
|
291
|
-
});
|
|
171
|
+
act(() => result.current.clearError());
|
|
292
172
|
|
|
293
|
-
|
|
294
|
-
});
|
|
173
|
+
expect(result.current.error).toBeNull();
|
|
295
174
|
});
|
|
296
175
|
});
|
|
@@ -29,8 +29,9 @@ vi.mock("../../execution/useCreateAgentExecution", () => ({
|
|
|
29
29
|
const mockDefaultAgent = {
|
|
30
30
|
agent: null as { status?: { defaultInstanceId?: string } } | null,
|
|
31
31
|
isLoading: false,
|
|
32
|
-
error: null,
|
|
32
|
+
error: null as Error | null,
|
|
33
33
|
refetch: vi.fn(),
|
|
34
|
+
waitForResolution: vi.fn<() => Promise<unknown>>(),
|
|
34
35
|
};
|
|
35
36
|
vi.mock("../../agent", () => ({
|
|
36
37
|
useDefaultAgent: () => mockDefaultAgent,
|
|
@@ -379,4 +380,98 @@ describe("useNewSessionFlow", () => {
|
|
|
379
380
|
expect(result.current.isSubmitting).toBe(false);
|
|
380
381
|
});
|
|
381
382
|
});
|
|
383
|
+
|
|
384
|
+
describe("submit while default agent is loading", () => {
|
|
385
|
+
it("awaits default agent and creates session when fetch resolves", async () => {
|
|
386
|
+
const resolvedAgent = { status: { defaultInstanceId: "awaited-inst" } };
|
|
387
|
+
mockDefaultAgent.agent = null;
|
|
388
|
+
mockDefaultAgent.isLoading = true;
|
|
389
|
+
mockDefaultAgent.error = null;
|
|
390
|
+
mockDefaultAgent.waitForResolution.mockResolvedValue(resolvedAgent);
|
|
391
|
+
|
|
392
|
+
const opts = defaultOptions();
|
|
393
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
394
|
+
|
|
395
|
+
await act(async () => {
|
|
396
|
+
await result.current.submit("Hello");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(mockDefaultAgent.waitForResolution).toHaveBeenCalledOnce();
|
|
400
|
+
expect(mockCreateSession).toHaveBeenCalledOnce();
|
|
401
|
+
expect(mockCreateSession.mock.calls[0][0].agentInstanceId).toBe("awaited-inst");
|
|
402
|
+
expect(opts.onSessionCreated).toHaveBeenCalledWith("sess-new");
|
|
403
|
+
expect(result.current.submitError).toBeNull();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("surfaces error when fetch fails during await", async () => {
|
|
407
|
+
mockDefaultAgent.agent = null;
|
|
408
|
+
mockDefaultAgent.isLoading = true;
|
|
409
|
+
mockDefaultAgent.error = null;
|
|
410
|
+
mockDefaultAgent.waitForResolution.mockRejectedValue(
|
|
411
|
+
new Error("Network failure"),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const opts = defaultOptions();
|
|
415
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
416
|
+
|
|
417
|
+
await act(async () => {
|
|
418
|
+
await result.current.submit("Hello");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
expect(result.current.submitError).toBeTruthy();
|
|
422
|
+
expect(opts.onError).toHaveBeenCalled();
|
|
423
|
+
expect(mockCreateSession).not.toHaveBeenCalled();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("surfaces timeout error when fetch never resolves", async () => {
|
|
427
|
+
vi.useFakeTimers();
|
|
428
|
+
|
|
429
|
+
mockDefaultAgent.agent = null;
|
|
430
|
+
mockDefaultAgent.isLoading = true;
|
|
431
|
+
mockDefaultAgent.error = null;
|
|
432
|
+
mockDefaultAgent.waitForResolution.mockReturnValue(
|
|
433
|
+
new Promise(() => {}),
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const opts = defaultOptions();
|
|
437
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
438
|
+
|
|
439
|
+
let submitPromise: Promise<void>;
|
|
440
|
+
act(() => {
|
|
441
|
+
submitPromise = result.current.submit("Hello") as unknown as Promise<void>;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
await act(async () => {
|
|
445
|
+
vi.advanceTimersByTime(10_000);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await act(async () => {
|
|
449
|
+
await submitPromise;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(result.current.submitError).toContain("did not load in time");
|
|
453
|
+
expect(opts.onError).toHaveBeenCalled();
|
|
454
|
+
expect(mockCreateSession).not.toHaveBeenCalled();
|
|
455
|
+
|
|
456
|
+
vi.useRealTimers();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("errors immediately when default agent has already failed", async () => {
|
|
460
|
+
mockDefaultAgent.agent = null;
|
|
461
|
+
mockDefaultAgent.isLoading = false;
|
|
462
|
+
mockDefaultAgent.error = new Error("Already failed");
|
|
463
|
+
|
|
464
|
+
const opts = defaultOptions();
|
|
465
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
466
|
+
|
|
467
|
+
await act(async () => {
|
|
468
|
+
await result.current.submit("Hello");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(result.current.submitError).toContain("Failed to load default agent");
|
|
472
|
+
expect(opts.onError).toHaveBeenCalled();
|
|
473
|
+
expect(mockCreateSession).not.toHaveBeenCalled();
|
|
474
|
+
expect(mockDefaultAgent.waitForResolution).not.toHaveBeenCalled();
|
|
475
|
+
});
|
|
476
|
+
});
|
|
382
477
|
});
|