@stigmer/react 3.1.13 → 3.1.14
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/AgentDetailView.d.ts +7 -9
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +22 -30
- package/agent/AgentDetailView.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/sharing/AgentShareList.d.ts +44 -0
- package/sharing/AgentShareList.d.ts.map +1 -0
- package/sharing/AgentShareList.js +149 -0
- package/sharing/AgentShareList.js.map +1 -0
- package/sharing/ShareAgentDialog.d.ts +38 -25
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +131 -62
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/index.d.ts +10 -8
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +5 -4
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShares.d.ts +42 -0
- package/sharing/useAgentShares.d.ts.map +1 -0
- package/sharing/useAgentShares.js +38 -0
- package/sharing/useAgentShares.js.map +1 -0
- package/sharing/useCanCreateAgentShare.d.ts +43 -0
- package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
- package/sharing/useCanCreateAgentShare.js +44 -0
- package/sharing/useCanCreateAgentShare.js.map +1 -0
- package/sharing/useDeleteAgentShare.d.ts +42 -0
- package/sharing/useDeleteAgentShare.d.ts.map +1 -0
- package/sharing/useDeleteAgentShare.js +45 -0
- package/sharing/useDeleteAgentShare.js.map +1 -0
- package/sharing/useSaveAgentShare.d.ts +43 -18
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +55 -22
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +34 -43
- package/src/index.ts +11 -9
- package/src/sharing/AgentShareList.tsx +507 -0
- package/src/sharing/ShareAgentDialog.tsx +310 -112
- package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
- package/src/sharing/__tests__/useAgentShares.test.tsx +145 -0
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/index.ts +10 -12
- package/src/sharing/useAgentShares.ts +68 -0
- package/src/sharing/useCanCreateAgentShare.ts +74 -0
- package/src/sharing/useDeleteAgentShare.ts +73 -0
- package/src/sharing/useSaveAgentShare.ts +73 -23
- package/styles.css +1 -1
- package/sharing/useAgentShare.d.ts +0 -49
- package/sharing/useAgentShare.d.ts.map +0 -1
- package/sharing/useAgentShare.js +0 -64
- package/sharing/useAgentShare.js.map +0 -1
- package/sharing/useCreateExternalShareLink.d.ts +0 -90
- package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
- package/sharing/useCreateExternalShareLink.js +0 -65
- package/sharing/useCreateExternalShareLink.js.map +0 -1
- package/sharing/useShareAgent.d.ts +0 -69
- package/sharing/useShareAgent.d.ts.map +0 -1
- package/sharing/useShareAgent.js +0 -42
- package/sharing/useShareAgent.js.map +0 -1
- package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
- package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
- package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
- package/src/sharing/useAgentShare.ts +0 -105
- package/src/sharing/useCreateExternalShareLink.tsx +0 -141
- package/src/sharing/useShareAgent.tsx +0 -107
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent, waitFor, cleanup, within } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
|
|
5
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
6
|
+
import type { AgentShareInput } from "@stigmer/sdk";
|
|
7
|
+
import { StigmerContext } from "../../context";
|
|
8
|
+
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
9
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
10
|
+
import { AgentShareList } from "../AgentShareList";
|
|
11
|
+
|
|
12
|
+
// Toasts are visual feedback owned by the feedback module; keep them inert.
|
|
13
|
+
vi.mock("../../feedback/toast", () => ({
|
|
14
|
+
toast: { success: vi.fn(), error: vi.fn() },
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// happy-dom does not implement the native dialog show/close methods.
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
HTMLDialogElement.prototype.showModal = function showModal() {
|
|
20
|
+
this.open = true;
|
|
21
|
+
};
|
|
22
|
+
HTMLDialogElement.prototype.close = function close() {
|
|
23
|
+
this.open = false;
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(cleanup);
|
|
28
|
+
|
|
29
|
+
interface MockOverrides {
|
|
30
|
+
getByAgent?: (input: unknown) => Promise<unknown>;
|
|
31
|
+
apply?: (input: AgentShareInput) => Promise<unknown>;
|
|
32
|
+
deleteShare?: (id: string) => Promise<unknown>;
|
|
33
|
+
rotateShareLink?: (input: unknown) => Promise<unknown>;
|
|
34
|
+
isAuthorized?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createMockStigmer(overrides: MockOverrides = {}) {
|
|
38
|
+
return {
|
|
39
|
+
agentShare: {
|
|
40
|
+
getByAgent:
|
|
41
|
+
overrides.getByAgent ??
|
|
42
|
+
vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
|
|
43
|
+
apply: overrides.apply ?? vi.fn().mockResolvedValue({}),
|
|
44
|
+
delete: overrides.deleteShare ?? vi.fn().mockResolvedValue({}),
|
|
45
|
+
rotateShareLink:
|
|
46
|
+
overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
|
|
47
|
+
},
|
|
48
|
+
iamPolicy: {
|
|
49
|
+
checkMyPermission: vi.fn().mockResolvedValue({
|
|
50
|
+
isAuthorized: overrides.isAuthorized ?? true,
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
environment: {
|
|
54
|
+
list: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
|
55
|
+
getByReference: vi.fn().mockRejectedValue(new Error("not found")),
|
|
56
|
+
},
|
|
57
|
+
billing: {
|
|
58
|
+
getOrCreateBillingAccount: vi.fn().mockResolvedValue(null),
|
|
59
|
+
},
|
|
60
|
+
} as never;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Providers({
|
|
64
|
+
client,
|
|
65
|
+
children,
|
|
66
|
+
}: {
|
|
67
|
+
client: unknown;
|
|
68
|
+
children: ReactNode;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<FetchCacheContext.Provider value={null}>
|
|
72
|
+
<DeploymentModeContext.Provider value="cloud">
|
|
73
|
+
<StigmerContext.Provider value={client as never}>
|
|
74
|
+
{children}
|
|
75
|
+
</StigmerContext.Provider>
|
|
76
|
+
</DeploymentModeContext.Provider>
|
|
77
|
+
</FetchCacheContext.Provider>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const AGENT = {
|
|
82
|
+
metadata: {
|
|
83
|
+
id: "agt_1",
|
|
84
|
+
org: "acme",
|
|
85
|
+
slug: "support-agent",
|
|
86
|
+
name: "Support Agent",
|
|
87
|
+
visibility: ApiResourceVisibility.visibility_public,
|
|
88
|
+
},
|
|
89
|
+
spec: { mcpServerUsages: [] },
|
|
90
|
+
} as never;
|
|
91
|
+
|
|
92
|
+
function makeShare(overrides?: {
|
|
93
|
+
id?: string;
|
|
94
|
+
org?: string;
|
|
95
|
+
slug?: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
enabled?: boolean;
|
|
98
|
+
audience?: AgentShareAudience;
|
|
99
|
+
allowedOrigins?: string[];
|
|
100
|
+
shareLinkToken?: string;
|
|
101
|
+
}) {
|
|
102
|
+
return {
|
|
103
|
+
metadata: {
|
|
104
|
+
id: overrides?.id ?? "ash_1",
|
|
105
|
+
org: overrides?.org ?? "acme",
|
|
106
|
+
slug: overrides?.slug ?? "support-agent",
|
|
107
|
+
name: overrides?.name ?? "Support Agent",
|
|
108
|
+
},
|
|
109
|
+
spec: {
|
|
110
|
+
agentRef: { org: "acme", slug: "support-agent" },
|
|
111
|
+
enabled: overrides?.enabled ?? true,
|
|
112
|
+
...(overrides?.audience !== undefined && { audience: overrides.audience }),
|
|
113
|
+
...(overrides?.allowedOrigins && { allowedOrigins: overrides.allowedOrigins }),
|
|
114
|
+
},
|
|
115
|
+
...(overrides?.shareLinkToken !== undefined
|
|
116
|
+
? { status: { shareLinkToken: overrides.shareLinkToken } }
|
|
117
|
+
: {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function withShares(...shares: unknown[]) {
|
|
122
|
+
return vi.fn().mockResolvedValue({ totalCount: shares.length, items: shares });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const buildShareUrl = (org: string, slug: string) =>
|
|
126
|
+
`https://app.example.com/chat/${org}/${slug}`;
|
|
127
|
+
|
|
128
|
+
async function renderList(
|
|
129
|
+
client: unknown,
|
|
130
|
+
props?: Partial<Parameters<typeof AgentShareList>[0]>,
|
|
131
|
+
) {
|
|
132
|
+
render(
|
|
133
|
+
<Providers client={client}>
|
|
134
|
+
<AgentShareList
|
|
135
|
+
agent={AGENT}
|
|
136
|
+
buildShareUrl={buildShareUrl}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
</Providers>,
|
|
140
|
+
);
|
|
141
|
+
await waitFor(() =>
|
|
142
|
+
expect(document.querySelector('[class*="animate-pulse"]')).toBeNull(),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe("AgentShareList", () => {
|
|
147
|
+
it("lists every share with link, audience, and status — no canonical collapse", async () => {
|
|
148
|
+
const client = createMockStigmer({
|
|
149
|
+
getByAgent: withShares(
|
|
150
|
+
makeShare(),
|
|
151
|
+
makeShare({
|
|
152
|
+
id: "ash_2",
|
|
153
|
+
slug: "help-desk",
|
|
154
|
+
name: "Help Desk",
|
|
155
|
+
enabled: false,
|
|
156
|
+
audience: AgentShareAudience.org,
|
|
157
|
+
}),
|
|
158
|
+
),
|
|
159
|
+
});
|
|
160
|
+
await renderList(client);
|
|
161
|
+
|
|
162
|
+
expect(screen.getByText("2 shares")).toBeTruthy();
|
|
163
|
+
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
164
|
+
expect(screen.getByText("Help Desk")).toBeTruthy();
|
|
165
|
+
expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
|
|
166
|
+
expect(screen.getByText("/chat/acme/help-desk")).toBeTruthy();
|
|
167
|
+
expect(screen.getByText("Public")).toBeTruthy();
|
|
168
|
+
expect(screen.getByText("Org members")).toBeTruthy();
|
|
169
|
+
expect(screen.getByText("Active")).toBeTruthy();
|
|
170
|
+
expect(screen.getByText("Paused")).toBeTruthy();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("marks another org's channel of this agent as cross-org", async () => {
|
|
174
|
+
const client = createMockStigmer({
|
|
175
|
+
getByAgent: withShares(
|
|
176
|
+
makeShare({ id: "ash_ext", org: "consumer-org" }),
|
|
177
|
+
),
|
|
178
|
+
});
|
|
179
|
+
await renderList(client);
|
|
180
|
+
|
|
181
|
+
expect(screen.getByText("Cross-org")).toBeTruthy();
|
|
182
|
+
expect(screen.getByText("/chat/consumer-org/support-agent")).toBeTruthy();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("shows the empty state with a create call-to-action when allowed", async () => {
|
|
186
|
+
const client = createMockStigmer();
|
|
187
|
+
await renderList(client);
|
|
188
|
+
|
|
189
|
+
expect(screen.getByText("No shares yet")).toBeTruthy();
|
|
190
|
+
expect(
|
|
191
|
+
screen.getByRole("button", { name: /Create share/ }),
|
|
192
|
+
).toBeTruthy();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("hides the create affordances when the viewer fails the create bar", async () => {
|
|
196
|
+
const client = createMockStigmer({ isAuthorized: false });
|
|
197
|
+
await renderList(client);
|
|
198
|
+
|
|
199
|
+
expect(screen.getByText("No shares yet")).toBeTruthy();
|
|
200
|
+
expect(screen.queryByRole("button", { name: /Create share/ })).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("opens the dialog in create mode from the Create button", async () => {
|
|
204
|
+
const client = createMockStigmer({ getByAgent: withShares(makeShare()) });
|
|
205
|
+
await renderList(client);
|
|
206
|
+
|
|
207
|
+
fireEvent.click(screen.getByRole("button", { name: /Create share/ }));
|
|
208
|
+
|
|
209
|
+
// The dialog's create step renders identity fields.
|
|
210
|
+
expect(await screen.findByLabelText("Name", { selector: "input" })).toBeTruthy();
|
|
211
|
+
expect(screen.getByLabelText("Slug", { selector: "input" })).toBeTruthy();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("opens the dialog on the EXACT share of the clicked row", async () => {
|
|
215
|
+
const client = createMockStigmer({
|
|
216
|
+
getByAgent: withShares(
|
|
217
|
+
makeShare(),
|
|
218
|
+
makeShare({ id: "ash_2", slug: "help-desk", name: "Help Desk" }),
|
|
219
|
+
),
|
|
220
|
+
});
|
|
221
|
+
await renderList(client);
|
|
222
|
+
|
|
223
|
+
const row = screen.getByText("Help Desk").closest("tr")!;
|
|
224
|
+
fireEvent.click(within(row).getByRole("button", { name: "Edit" }));
|
|
225
|
+
|
|
226
|
+
// The editor shows the chosen share's URL — not the first row's.
|
|
227
|
+
expect(
|
|
228
|
+
await screen.findByText("https://app.example.com/chat/acme/help-desk"),
|
|
229
|
+
).toBeTruthy();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("copies the tokened URL for public shares and the clean URL for org shares", async () => {
|
|
233
|
+
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
234
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
235
|
+
value: { writeText },
|
|
236
|
+
configurable: true,
|
|
237
|
+
});
|
|
238
|
+
const client = createMockStigmer({
|
|
239
|
+
getByAgent: withShares(
|
|
240
|
+
makeShare({ shareLinkToken: "tok123" }),
|
|
241
|
+
makeShare({
|
|
242
|
+
id: "ash_2",
|
|
243
|
+
slug: "internal",
|
|
244
|
+
name: "Internal",
|
|
245
|
+
audience: AgentShareAudience.org,
|
|
246
|
+
shareLinkToken: "tok456",
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
});
|
|
250
|
+
await renderList(client);
|
|
251
|
+
|
|
252
|
+
fireEvent.click(
|
|
253
|
+
screen.getByRole("button", { name: "Copy link for Support Agent" }),
|
|
254
|
+
);
|
|
255
|
+
await waitFor(() =>
|
|
256
|
+
expect(writeText).toHaveBeenCalledWith(
|
|
257
|
+
"https://app.example.com/chat/acme/support-agent?k=tok123",
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
fireEvent.click(
|
|
262
|
+
screen.getByRole("button", { name: "Copy link for Internal" }),
|
|
263
|
+
);
|
|
264
|
+
// Org access is gated by membership, not the token — the member link
|
|
265
|
+
// stays clean.
|
|
266
|
+
await waitFor(() =>
|
|
267
|
+
expect(writeText).toHaveBeenCalledWith(
|
|
268
|
+
"https://app.example.com/chat/acme/internal",
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("pause / resume", () => {
|
|
274
|
+
it("pauses by applying the FULL spec with only enabled flipped", async () => {
|
|
275
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
276
|
+
const client = createMockStigmer({
|
|
277
|
+
apply,
|
|
278
|
+
getByAgent: withShares(
|
|
279
|
+
makeShare({ allowedOrigins: ["https://example.com"] }),
|
|
280
|
+
),
|
|
281
|
+
});
|
|
282
|
+
await renderList(client);
|
|
283
|
+
|
|
284
|
+
fireEvent.click(screen.getByRole("button", { name: "Pause" }));
|
|
285
|
+
|
|
286
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
287
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
288
|
+
expect(input.enabled).toBe(false);
|
|
289
|
+
// Apply replaces the spec wholesale — the toggle must carry the
|
|
290
|
+
// existing config or it would silently erase it.
|
|
291
|
+
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
292
|
+
// Keyed on the row's own identity — never a new row.
|
|
293
|
+
expect(input.org).toBe("acme");
|
|
294
|
+
expect(input.slug).toBe("support-agent");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("resumes a paused share", async () => {
|
|
298
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
299
|
+
const client = createMockStigmer({
|
|
300
|
+
apply,
|
|
301
|
+
getByAgent: withShares(makeShare({ enabled: false })),
|
|
302
|
+
});
|
|
303
|
+
await renderList(client);
|
|
304
|
+
|
|
305
|
+
fireEvent.click(screen.getByRole("button", { name: "Resume" }));
|
|
306
|
+
|
|
307
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
308
|
+
expect((apply.mock.calls[0][0] as AgentShareInput).enabled).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("reset link", () => {
|
|
313
|
+
it("rotates the row's own share", async () => {
|
|
314
|
+
const rotateShareLink = vi.fn().mockResolvedValue(makeShare());
|
|
315
|
+
const client = createMockStigmer({
|
|
316
|
+
rotateShareLink,
|
|
317
|
+
getByAgent: withShares(makeShare()),
|
|
318
|
+
});
|
|
319
|
+
await renderList(client);
|
|
320
|
+
|
|
321
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset link" }));
|
|
322
|
+
|
|
323
|
+
await waitFor(() => expect(rotateShareLink).toHaveBeenCalledTimes(1));
|
|
324
|
+
expect(
|
|
325
|
+
(rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
|
|
326
|
+
).toBe("ash_1");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("is not offered on org-audience shares (membership gates access, not the token)", async () => {
|
|
330
|
+
const client = createMockStigmer({
|
|
331
|
+
getByAgent: withShares(
|
|
332
|
+
makeShare({ audience: AgentShareAudience.org }),
|
|
333
|
+
),
|
|
334
|
+
});
|
|
335
|
+
await renderList(client);
|
|
336
|
+
|
|
337
|
+
expect(screen.queryByRole("button", { name: "Reset link" })).toBeNull();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe("delete", () => {
|
|
342
|
+
it("deletes after confirmation and refetches", async () => {
|
|
343
|
+
const deleteShare = vi.fn().mockResolvedValue({});
|
|
344
|
+
const getByAgent = withShares(makeShare());
|
|
345
|
+
const client = createMockStigmer({ deleteShare, getByAgent });
|
|
346
|
+
await renderList(client);
|
|
347
|
+
|
|
348
|
+
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
|
|
349
|
+
|
|
350
|
+
// The confirmation names the destructive consequence and the
|
|
351
|
+
// config-preserving alternative (pause).
|
|
352
|
+
const title = await screen.findByText("Delete share?");
|
|
353
|
+
expect(screen.getByText(/pause it instead/i)).toBeTruthy();
|
|
354
|
+
|
|
355
|
+
const confirmDialog = title.closest("dialog") as HTMLElement;
|
|
356
|
+
fireEvent.click(
|
|
357
|
+
within(confirmDialog).getByRole("button", { name: "Delete", hidden: true }),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
await waitFor(() => expect(deleteShare).toHaveBeenCalledWith("ash_1"));
|
|
361
|
+
await waitFor(() => expect(getByAgent).toHaveBeenCalledTimes(2));
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("does nothing when the confirmation is cancelled", async () => {
|
|
365
|
+
const deleteShare = vi.fn();
|
|
366
|
+
const client = createMockStigmer({
|
|
367
|
+
deleteShare,
|
|
368
|
+
getByAgent: withShares(makeShare()),
|
|
369
|
+
});
|
|
370
|
+
await renderList(client);
|
|
371
|
+
|
|
372
|
+
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
|
|
373
|
+
expect(await screen.findByText("Delete share?")).toBeTruthy();
|
|
374
|
+
|
|
375
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel", hidden: true }));
|
|
376
|
+
expect(deleteShare).not.toHaveBeenCalled();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("shows an error state when the share list fails to load", async () => {
|
|
381
|
+
const client = createMockStigmer({
|
|
382
|
+
getByAgent: vi.fn().mockRejectedValue(new Error("backend unavailable")),
|
|
383
|
+
});
|
|
384
|
+
await renderList(client);
|
|
385
|
+
|
|
386
|
+
expect(screen.getByText("Failed to load shares")).toBeTruthy();
|
|
387
|
+
});
|
|
388
|
+
});
|