@stigmer/react 3.1.12 → 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 +11 -3
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +22 -17
- 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 -15
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +135 -57
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/index.d.ts +10 -6
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +5 -3
- 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 +45 -14
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +59 -19
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +37 -20
- package/src/index.ts +11 -6
- package/src/sharing/AgentShareList.tsx +507 -0
- package/src/sharing/ShareAgentDialog.tsx +325 -95
- package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +333 -286
- package/src/sharing/__tests__/{useAgentShare.test.tsx → useAgentShares.test.tsx} +47 -51
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +48 -0
- package/src/sharing/index.ts +10 -7
- 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 +77 -19
- package/styles.css +1 -1
- package/sharing/useAgentShare.d.ts +0 -43
- package/sharing/useAgentShare.d.ts.map +0 -1
- package/sharing/useAgentShare.js +0 -54
- package/sharing/useAgentShare.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__/useShareAgent.test.tsx +0 -131
- package/src/sharing/useAgentShare.ts +0 -91
- package/src/sharing/useShareAgent.tsx +0 -107
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
2
|
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
+
import { Code } from "@connectrpc/connect";
|
|
4
5
|
import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
|
|
5
6
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
6
|
-
import type
|
|
7
|
+
import { StigmerError, type AgentShareInput } from "@stigmer/sdk";
|
|
7
8
|
import { StigmerContext } from "../../context";
|
|
8
9
|
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
9
10
|
import { DeploymentModeContext } from "../../deployment-mode";
|
|
@@ -27,7 +28,6 @@ beforeAll(() => {
|
|
|
27
28
|
afterEach(cleanup);
|
|
28
29
|
|
|
29
30
|
interface MockOverrides {
|
|
30
|
-
getByAgent?: (input: unknown) => Promise<unknown>;
|
|
31
31
|
apply?: (input: AgentShareInput) => Promise<unknown>;
|
|
32
32
|
rotateShareLink?: (input: unknown) => Promise<unknown>;
|
|
33
33
|
getOrCreateBillingAccount?: (orgId: string) => Promise<unknown>;
|
|
@@ -37,9 +37,6 @@ interface MockOverrides {
|
|
|
37
37
|
function createMockStigmer(overrides: MockOverrides = {}) {
|
|
38
38
|
return {
|
|
39
39
|
agentShare: {
|
|
40
|
-
getByAgent:
|
|
41
|
-
overrides.getByAgent ??
|
|
42
|
-
vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
|
|
43
40
|
apply: overrides.apply ?? vi.fn().mockResolvedValue({}),
|
|
44
41
|
rotateShareLink:
|
|
45
42
|
overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
|
|
@@ -129,11 +126,7 @@ function makeShare(
|
|
|
129
126
|
},
|
|
130
127
|
spec: { agentRef: { org: "acme", slug: "support-agent" }, ...spec },
|
|
131
128
|
...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function withShare(share: unknown) {
|
|
136
|
-
return vi.fn().mockResolvedValue({ totalCount: 1, items: [share] });
|
|
129
|
+
} as never;
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
function orgSharedEnv(slug: string, name?: string) {
|
|
@@ -150,8 +143,8 @@ function orgSharedEnv(slug: string, name?: string) {
|
|
|
150
143
|
const buildShareUrl = (org: string, slug: string) =>
|
|
151
144
|
`https://app.example.com/chat/${org}/${slug}`;
|
|
152
145
|
|
|
153
|
-
/** Render the open
|
|
154
|
-
|
|
146
|
+
/** Render the dialog open. Pass `share` for edit mode; omit for create mode. */
|
|
147
|
+
function renderOpenDialog(
|
|
155
148
|
client: unknown,
|
|
156
149
|
props?: Partial<Parameters<typeof ShareAgentDialog>[0]> & { mode?: "cloud" | "local" },
|
|
157
150
|
) {
|
|
@@ -167,96 +160,31 @@ async function renderOpenDialog(
|
|
|
167
160
|
/>
|
|
168
161
|
</Providers>,
|
|
169
162
|
);
|
|
170
|
-
await waitFor(() =>
|
|
171
|
-
expect(screen.queryByLabelText("Loading sharing settings")).toBeNull(),
|
|
172
|
-
);
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
describe("ShareAgentDialog", () => {
|
|
176
|
-
it("mounts no body while closed (
|
|
177
|
-
const getByAgent = vi.fn();
|
|
166
|
+
it("mounts no body while closed (billing fetch stays lazy)", () => {
|
|
178
167
|
const getOrCreateBillingAccount = vi.fn();
|
|
179
168
|
render(
|
|
180
|
-
<Providers
|
|
181
|
-
client={createMockStigmer({ getByAgent, getOrCreateBillingAccount })}
|
|
182
|
-
>
|
|
169
|
+
<Providers client={createMockStigmer({ getOrCreateBillingAccount })}>
|
|
183
170
|
<ShareAgentDialog
|
|
184
171
|
open={false}
|
|
185
172
|
onOpenChange={() => {}}
|
|
186
173
|
agent={makeAgent()}
|
|
174
|
+
share={makeShare({ enabled: true })}
|
|
187
175
|
buildShareUrl={buildShareUrl}
|
|
188
176
|
/>
|
|
189
177
|
</Providers>,
|
|
190
178
|
);
|
|
191
179
|
|
|
192
180
|
expect(screen.queryByText("Share")).toBeNull();
|
|
193
|
-
expect(getByAgent).not.toHaveBeenCalled();
|
|
194
181
|
expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
|
|
195
182
|
});
|
|
196
183
|
|
|
197
|
-
it("
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
new Promise((resolve) => {
|
|
201
|
-
resolveLoad = resolve;
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
204
|
-
render(
|
|
205
|
-
<Providers client={createMockStigmer({ getByAgent })}>
|
|
206
|
-
<ShareAgentDialog
|
|
207
|
-
open
|
|
208
|
-
onOpenChange={() => {}}
|
|
209
|
-
agent={makeAgent()}
|
|
210
|
-
buildShareUrl={buildShareUrl}
|
|
211
|
-
/>
|
|
212
|
-
</Providers>,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
expect(screen.getByLabelText("Loading sharing settings")).toBeTruthy();
|
|
216
|
-
resolveLoad({ totalCount: 1, items: [makeShare({ enabled: true })] });
|
|
217
|
-
await waitFor(() =>
|
|
218
|
-
expect(
|
|
219
|
-
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
220
|
-
).toBeTruthy(),
|
|
221
|
-
);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("offers retry when the share load fails", async () => {
|
|
225
|
-
const share = makeShare({ enabled: true });
|
|
226
|
-
const getByAgent = vi
|
|
227
|
-
.fn()
|
|
228
|
-
.mockRejectedValueOnce(new Error("backend unavailable"))
|
|
229
|
-
.mockResolvedValueOnce({ totalCount: 1, items: [share] });
|
|
230
|
-
render(
|
|
231
|
-
<Providers client={createMockStigmer({ getByAgent })}>
|
|
232
|
-
<ShareAgentDialog
|
|
233
|
-
open
|
|
234
|
-
onOpenChange={() => {}}
|
|
235
|
-
agent={makeAgent()}
|
|
236
|
-
buildShareUrl={buildShareUrl}
|
|
237
|
-
/>
|
|
238
|
-
</Providers>,
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
// A failed load must never render a form that would create a share
|
|
242
|
-
// over an existing one the dialog couldn't see.
|
|
243
|
-
expect(
|
|
244
|
-
await screen.findByText(/couldn't load this agent's sharing settings/i),
|
|
245
|
-
).toBeTruthy();
|
|
246
|
-
|
|
247
|
-
fireEvent.click(screen.getByRole("button", { name: "Try again", hidden: true }));
|
|
248
|
-
await waitFor(() =>
|
|
249
|
-
expect(
|
|
250
|
-
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
251
|
-
).toBeTruthy(),
|
|
252
|
-
);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("renders header, toggle, and the share link from buildShareUrl", async () => {
|
|
256
|
-
const client = createMockStigmer({
|
|
257
|
-
getByAgent: withShare(makeShare({ enabled: true })),
|
|
184
|
+
it("renders header, toggle, and the share link from buildShareUrl", () => {
|
|
185
|
+
renderOpenDialog(createMockStigmer(), {
|
|
186
|
+
share: makeShare({ enabled: true }),
|
|
258
187
|
});
|
|
259
|
-
await renderOpenDialog(client);
|
|
260
188
|
|
|
261
189
|
expect(screen.getByText("Share")).toBeTruthy();
|
|
262
190
|
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
@@ -266,77 +194,190 @@ describe("ShareAgentDialog", () => {
|
|
|
266
194
|
).toBeTruthy();
|
|
267
195
|
});
|
|
268
196
|
|
|
269
|
-
it("builds the link from the SHARE's slug when it differs from the agent's",
|
|
270
|
-
// A
|
|
197
|
+
it("builds the link from the SHARE's slug when it differs from the agent's", () => {
|
|
198
|
+
// A renamed share: the hosted URL lives at the share's slug.
|
|
271
199
|
const renamed = {
|
|
272
|
-
...makeShare({ enabled: true }),
|
|
200
|
+
...(makeShare({ enabled: true }) as Record<string, unknown>),
|
|
273
201
|
metadata: { id: "ash_1", org: "acme", slug: "help-desk", name: "Help Desk" },
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
await renderOpenDialog(client);
|
|
202
|
+
} as never;
|
|
203
|
+
renderOpenDialog(createMockStigmer(), { share: renamed });
|
|
277
204
|
|
|
278
205
|
expect(
|
|
279
206
|
screen.getByText("https://app.example.com/chat/acme/help-desk"),
|
|
280
207
|
).toBeTruthy();
|
|
281
208
|
});
|
|
282
209
|
|
|
283
|
-
it("falls back to the relative /chat path when buildShareUrl is omitted",
|
|
284
|
-
|
|
285
|
-
|
|
210
|
+
it("falls back to the relative /chat path when buildShareUrl is omitted", () => {
|
|
211
|
+
renderOpenDialog(createMockStigmer(), {
|
|
212
|
+
share: makeShare({ enabled: true }),
|
|
213
|
+
buildShareUrl: undefined,
|
|
286
214
|
});
|
|
287
|
-
await renderOpenDialog(client, { buildShareUrl: undefined });
|
|
288
215
|
|
|
289
216
|
expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
|
|
290
217
|
});
|
|
291
218
|
|
|
292
|
-
describe("
|
|
293
|
-
it("renders the
|
|
294
|
-
|
|
295
|
-
await renderOpenDialog(client);
|
|
219
|
+
describe("create mode (no share prop)", () => {
|
|
220
|
+
it("renders the create step with identity prefilled from the agent", () => {
|
|
221
|
+
renderOpenDialog(createMockStigmer());
|
|
296
222
|
|
|
297
223
|
expect(
|
|
298
|
-
screen.getByRole("
|
|
299
|
-
).toBe("false");
|
|
300
|
-
const copy = screen.getByRole("button", {
|
|
301
|
-
name: "Copy",
|
|
302
|
-
hidden: true,
|
|
303
|
-
}) as HTMLButtonElement;
|
|
304
|
-
expect(copy.disabled).toBe(true);
|
|
305
|
-
expect(
|
|
306
|
-
screen.getByText(/sharing is off, so this link doesn't work yet/i),
|
|
224
|
+
screen.getByRole("heading", { name: "Create share", hidden: true }),
|
|
307
225
|
).toBeTruthy();
|
|
226
|
+
expect(
|
|
227
|
+
(screen.getByLabelText("Name", { selector: "input" }) as HTMLInputElement).value,
|
|
228
|
+
).toBe("Support Agent");
|
|
229
|
+
expect(
|
|
230
|
+
(screen.getByLabelText("Slug", { selector: "input" }) as HTMLInputElement).value,
|
|
231
|
+
).toBe("support-agent");
|
|
232
|
+
// No channel exists yet — no switch, no link, and the footer offers
|
|
233
|
+
// Cancel rather than Done.
|
|
234
|
+
expect(screen.queryByRole("switch", { hidden: true })).toBeNull();
|
|
235
|
+
expect(screen.getByText("Cancel")).toBeTruthy();
|
|
308
236
|
});
|
|
309
237
|
|
|
310
|
-
it("
|
|
238
|
+
it("auto-derives the slug from the name until the slug is edited", () => {
|
|
239
|
+
renderOpenDialog(createMockStigmer());
|
|
240
|
+
|
|
241
|
+
const name = screen.getByLabelText("Name", { selector: "input" });
|
|
242
|
+
const slug = screen.getByLabelText("Slug", { selector: "input" }) as HTMLInputElement;
|
|
243
|
+
|
|
244
|
+
fireEvent.change(name, { target: { value: "Docs Site Widget" } });
|
|
245
|
+
expect(slug.value).toBe("docs-site-widget");
|
|
246
|
+
|
|
247
|
+
fireEvent.change(slug, { target: { value: "docs-widget" } });
|
|
248
|
+
fireEvent.change(name, { target: { value: "Renamed Again" } });
|
|
249
|
+
// Touched slug stays put.
|
|
250
|
+
expect(slug.value).toBe("docs-widget");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("creates the share live with the chosen identity, then becomes its editor", async () => {
|
|
311
254
|
const apply = vi.fn().mockResolvedValue(makeShare({ enabled: true }));
|
|
312
|
-
|
|
313
|
-
await renderOpenDialog(client);
|
|
255
|
+
renderOpenDialog(createMockStigmer({ apply }));
|
|
314
256
|
|
|
315
|
-
fireEvent.click(screen.getByRole("
|
|
257
|
+
fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
|
|
316
258
|
|
|
317
259
|
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
318
260
|
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
319
|
-
// Create identity: the agent's own org/slug/name (the server's D2
|
|
320
|
-
// default made explicit) plus the agent reference.
|
|
321
261
|
expect(input.org).toBe("acme");
|
|
322
262
|
expect(input.slug).toBe("support-agent");
|
|
263
|
+
expect(input.name).toBe("Support Agent");
|
|
323
264
|
expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
|
|
265
|
+
// One intent-click yields a live link: created enabled, public.
|
|
324
266
|
expect(input.enabled).toBe(true);
|
|
267
|
+
expect(input.audience).toBe(AgentShareAudience.public);
|
|
268
|
+
|
|
269
|
+
// The dialog transitions to the editor on the created share.
|
|
270
|
+
await waitFor(() =>
|
|
271
|
+
expect(
|
|
272
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
273
|
+
).toBeTruthy(),
|
|
274
|
+
);
|
|
275
|
+
expect(screen.getByText("Done")).toBeTruthy();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("pins an (org, slug) collision to the slug field with a pick-another-slug remedy", async () => {
|
|
279
|
+
const apply = vi
|
|
280
|
+
.fn()
|
|
281
|
+
.mockRejectedValue(
|
|
282
|
+
new StigmerError("already-exists", "duplicate slug", Code.AlreadyExists),
|
|
283
|
+
);
|
|
284
|
+
renderOpenDialog(createMockStigmer({ apply }));
|
|
285
|
+
|
|
286
|
+
fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
|
|
287
|
+
|
|
288
|
+
expect(
|
|
289
|
+
await screen.findByText(/pick a different slug/i),
|
|
290
|
+
).toBeTruthy();
|
|
291
|
+
// Still on the create step — nothing was created.
|
|
292
|
+
expect(screen.queryByRole("switch", { hidden: true })).toBeNull();
|
|
293
|
+
|
|
294
|
+
// Editing the slug clears the collision so the user can retry.
|
|
295
|
+
fireEvent.change(screen.getByLabelText("Slug", { selector: "input" }), {
|
|
296
|
+
target: { value: "support-agent-2" },
|
|
297
|
+
});
|
|
298
|
+
expect(screen.queryByText(/pick a different slug/i)).toBeNull();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("surfaces other server refusals verbatim (e.g. non-public dependencies)", async () => {
|
|
302
|
+
const apply = vi
|
|
303
|
+
.fn()
|
|
304
|
+
.mockRejectedValue(
|
|
305
|
+
new StigmerError(
|
|
306
|
+
"failed-precondition",
|
|
307
|
+
"cannot share acme/support-agent across organizations: it references resources that are not public: skill acme/internal-kb",
|
|
308
|
+
Code.FailedPrecondition,
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
renderOpenDialog(createMockStigmer({ apply }), { shareOrg: "consumer-org" });
|
|
312
|
+
|
|
313
|
+
fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
|
|
314
|
+
|
|
315
|
+
expect(
|
|
316
|
+
await screen.findByText(/resources that are not public/i),
|
|
317
|
+
).toBeTruthy();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("cross-org create: qualifies the agent, names the paying org, creates in the viewer's org", async () => {
|
|
321
|
+
const apply = vi.fn().mockResolvedValue({
|
|
322
|
+
metadata: {
|
|
323
|
+
id: "ash_ext",
|
|
324
|
+
org: "consumer-org",
|
|
325
|
+
slug: "support-agent",
|
|
326
|
+
name: "Support Agent",
|
|
327
|
+
},
|
|
328
|
+
spec: {
|
|
329
|
+
agentRef: { org: "acme", slug: "support-agent" },
|
|
330
|
+
enabled: true,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
renderOpenDialog(createMockStigmer({ apply }), { shareOrg: "consumer-org" });
|
|
325
334
|
|
|
335
|
+
// The header names whose blueprint this channel serves.
|
|
336
|
+
expect(screen.getByText("acme/support-agent")).toBeTruthy();
|
|
337
|
+
// The create copy names the org that owns and pays for the channel.
|
|
338
|
+
expect(screen.getByText(/credits/)).toBeTruthy();
|
|
339
|
+
|
|
340
|
+
fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
|
|
341
|
+
|
|
342
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
343
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
344
|
+
expect(input.org).toBe("consumer-org");
|
|
345
|
+
expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
|
|
346
|
+
|
|
347
|
+
// The editor shows the sharing org's URL; cross-org shares are
|
|
348
|
+
// public-audience only, so no audience selector is offered.
|
|
326
349
|
await waitFor(() =>
|
|
327
350
|
expect(
|
|
328
|
-
screen.
|
|
329
|
-
).
|
|
351
|
+
screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
|
|
352
|
+
).toBeTruthy(),
|
|
330
353
|
);
|
|
354
|
+
expect(screen.queryByRole("radiogroup", { hidden: true })).toBeNull();
|
|
331
355
|
});
|
|
332
356
|
});
|
|
333
357
|
|
|
334
|
-
describe("
|
|
335
|
-
it("
|
|
336
|
-
|
|
337
|
-
|
|
358
|
+
describe("paused share (enabled: false)", () => {
|
|
359
|
+
it("renders the off state with disabled copy affordances", () => {
|
|
360
|
+
renderOpenDialog(createMockStigmer(), {
|
|
361
|
+
share: makeShare({ enabled: false }),
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(
|
|
365
|
+
screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
|
|
366
|
+
).toBe("false");
|
|
367
|
+
const copy = screen.getByRole("button", {
|
|
368
|
+
name: "Copy",
|
|
369
|
+
hidden: true,
|
|
370
|
+
}) as HTMLButtonElement;
|
|
371
|
+
expect(copy.disabled).toBe(true);
|
|
372
|
+
expect(
|
|
373
|
+
screen.getByText(/sharing is off, so this link doesn't work yet/i),
|
|
374
|
+
).toBeTruthy();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("shows the reason on the Embed tab too", () => {
|
|
378
|
+
renderOpenDialog(createMockStigmer(), {
|
|
379
|
+
share: makeShare({ enabled: false }),
|
|
338
380
|
});
|
|
339
|
-
await renderOpenDialog(client);
|
|
340
381
|
|
|
341
382
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
342
383
|
expect(
|
|
@@ -344,11 +385,10 @@ describe("ShareAgentDialog", () => {
|
|
|
344
385
|
).toBeTruthy();
|
|
345
386
|
});
|
|
346
387
|
|
|
347
|
-
it("drops the hint and enables copy once sharing is on",
|
|
348
|
-
|
|
349
|
-
|
|
388
|
+
it("drops the hint and enables copy once sharing is on", () => {
|
|
389
|
+
renderOpenDialog(createMockStigmer(), {
|
|
390
|
+
share: makeShare({ enabled: true }),
|
|
350
391
|
});
|
|
351
|
-
await renderOpenDialog(client);
|
|
352
392
|
|
|
353
393
|
const copy = screen.getByRole("button", {
|
|
354
394
|
name: "Copy",
|
|
@@ -359,11 +399,10 @@ describe("ShareAgentDialog", () => {
|
|
|
359
399
|
});
|
|
360
400
|
});
|
|
361
401
|
|
|
362
|
-
it("shows the indexability warning",
|
|
363
|
-
|
|
364
|
-
|
|
402
|
+
it("shows the indexability warning", () => {
|
|
403
|
+
renderOpenDialog(createMockStigmer(), {
|
|
404
|
+
share: makeShare({ enabled: true }),
|
|
365
405
|
});
|
|
366
|
-
await renderOpenDialog(client);
|
|
367
406
|
|
|
368
407
|
expect(
|
|
369
408
|
screen.getByText(/forwarded and indexed by search engines/),
|
|
@@ -373,17 +412,13 @@ describe("ShareAgentDialog", () => {
|
|
|
373
412
|
describe("Audience", () => {
|
|
374
413
|
it("defaults to Public link and switching to Org members applies the full spec", async () => {
|
|
375
414
|
const apply = vi.fn().mockResolvedValue({});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
messages: { rateLimited: "Easy there." },
|
|
383
|
-
}),
|
|
384
|
-
),
|
|
415
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
416
|
+
share: makeShare({
|
|
417
|
+
enabled: true,
|
|
418
|
+
allowedOrigins: ["https://example.com"],
|
|
419
|
+
messages: { rateLimited: "Easy there." },
|
|
420
|
+
}),
|
|
385
421
|
});
|
|
386
|
-
await renderOpenDialog(client);
|
|
387
422
|
|
|
388
423
|
const publicOption = screen.getByRole("radio", {
|
|
389
424
|
name: "Public link",
|
|
@@ -402,21 +437,25 @@ describe("ShareAgentDialog", () => {
|
|
|
402
437
|
expect(input.enabled).toBe(true);
|
|
403
438
|
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
404
439
|
expect(input.messages?.rateLimited).toBe("Easy there.");
|
|
440
|
+
// Edit keys on the existing share's identity — never a new row.
|
|
441
|
+
expect(input.org).toBe("acme");
|
|
442
|
+
expect(input.slug).toBe("support-agent");
|
|
405
443
|
});
|
|
406
444
|
|
|
407
445
|
it("switching to Org members drops credential bindings (public-audience only)", async () => {
|
|
408
446
|
const apply = vi.fn().mockResolvedValue({});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
447
|
+
renderOpenDialog(
|
|
448
|
+
createMockStigmer({
|
|
449
|
+
apply,
|
|
450
|
+
environments: [orgSharedEnv("github-creds")],
|
|
451
|
+
}),
|
|
452
|
+
{
|
|
453
|
+
share: makeShare({
|
|
413
454
|
enabled: true,
|
|
414
455
|
environmentRefs: [{ org: "acme", slug: "github-creds" }],
|
|
415
456
|
}),
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
});
|
|
419
|
-
await renderOpenDialog(client);
|
|
457
|
+
},
|
|
458
|
+
);
|
|
420
459
|
|
|
421
460
|
fireEvent.click(
|
|
422
461
|
screen.getByRole("radio", { name: "Org members", hidden: true }),
|
|
@@ -430,13 +469,10 @@ describe("ShareAgentDialog", () => {
|
|
|
430
469
|
expect(input.environmentRefs).toEqual([]);
|
|
431
470
|
});
|
|
432
471
|
|
|
433
|
-
it("renders org-audience copy: member link, revocation note, no indexability warning",
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
437
|
-
),
|
|
472
|
+
it("renders org-audience copy: member link, revocation note, no indexability warning", () => {
|
|
473
|
+
renderOpenDialog(createMockStigmer(), {
|
|
474
|
+
share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
438
475
|
});
|
|
439
|
-
await renderOpenDialog(client);
|
|
440
476
|
|
|
441
477
|
expect(screen.getByText("Organization members can chat")).toBeTruthy();
|
|
442
478
|
expect(screen.getByText("Member chat link")).toBeTruthy();
|
|
@@ -448,13 +484,10 @@ describe("ShareAgentDialog", () => {
|
|
|
448
484
|
).toBeNull();
|
|
449
485
|
});
|
|
450
486
|
|
|
451
|
-
it("replaces the Embed tab with a public-only explanation for the org audience",
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
455
|
-
),
|
|
487
|
+
it("replaces the Embed tab with a public-only explanation for the org audience", () => {
|
|
488
|
+
renderOpenDialog(createMockStigmer(), {
|
|
489
|
+
share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
456
490
|
});
|
|
457
|
-
await renderOpenDialog(client);
|
|
458
491
|
|
|
459
492
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
460
493
|
expect(
|
|
@@ -464,20 +497,60 @@ describe("ShareAgentDialog", () => {
|
|
|
464
497
|
});
|
|
465
498
|
});
|
|
466
499
|
|
|
500
|
+
describe("cross-org edit mode (share org differs from the agent's — decision 013)", () => {
|
|
501
|
+
const externalShare = {
|
|
502
|
+
metadata: {
|
|
503
|
+
id: "ash_ext",
|
|
504
|
+
org: "consumer-org",
|
|
505
|
+
slug: "support-agent",
|
|
506
|
+
name: "Support Agent",
|
|
507
|
+
},
|
|
508
|
+
spec: {
|
|
509
|
+
agentRef: { org: "acme", slug: "support-agent" },
|
|
510
|
+
enabled: true,
|
|
511
|
+
},
|
|
512
|
+
} as never;
|
|
513
|
+
|
|
514
|
+
it("qualifies the agent in the header and hides the audience selector", () => {
|
|
515
|
+
renderOpenDialog(createMockStigmer(), { share: externalShare });
|
|
516
|
+
|
|
517
|
+
// The header names whose blueprint this channel serves.
|
|
518
|
+
expect(screen.getByText("acme/support-agent")).toBeTruthy();
|
|
519
|
+
// Cross-org shares are public-audience only — no choice to offer.
|
|
520
|
+
expect(screen.queryByRole("radiogroup", { hidden: true })).toBeNull();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("builds the link and billing line from the sharing org", () => {
|
|
524
|
+
renderOpenDialog(createMockStigmer(), { share: externalShare });
|
|
525
|
+
|
|
526
|
+
expect(
|
|
527
|
+
screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
|
|
528
|
+
).toBeTruthy();
|
|
529
|
+
// Who-pays names the sharing org, not the agent's.
|
|
530
|
+
expect(screen.getByText("consumer-org")).toBeTruthy();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("same-org dialogs are unchanged when the share org equals the agent's", () => {
|
|
534
|
+
renderOpenDialog(createMockStigmer(), {
|
|
535
|
+
share: makeShare({ enabled: true }),
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
539
|
+
expect(screen.getByRole("radiogroup", { hidden: true })).toBeTruthy();
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
467
543
|
it("enabling applies the complete spec and notifies the host", async () => {
|
|
468
544
|
const apply = vi.fn().mockResolvedValue({});
|
|
469
545
|
const onSharingChanged = vi.fn();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}),
|
|
478
|
-
),
|
|
546
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
547
|
+
onSharingChanged,
|
|
548
|
+
share: makeShare({
|
|
549
|
+
enabled: false,
|
|
550
|
+
allowedOrigins: ["https://example.com"],
|
|
551
|
+
messages: { rateLimited: "Easy there." },
|
|
552
|
+
}),
|
|
479
553
|
});
|
|
480
|
-
await renderOpenDialog(client, { onSharingChanged });
|
|
481
554
|
|
|
482
555
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
483
556
|
|
|
@@ -499,11 +572,9 @@ describe("ShareAgentDialog", () => {
|
|
|
499
572
|
allowedOrigins: ["https://normalized.example.com"],
|
|
500
573
|
}),
|
|
501
574
|
);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
getByAgent: withShare(makeShare({ enabled: false })),
|
|
575
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
576
|
+
share: makeShare({ enabled: false }),
|
|
505
577
|
});
|
|
506
|
-
await renderOpenDialog(client);
|
|
507
578
|
|
|
508
579
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
509
580
|
await waitFor(() =>
|
|
@@ -517,11 +588,11 @@ describe("ShareAgentDialog", () => {
|
|
|
517
588
|
});
|
|
518
589
|
|
|
519
590
|
describe("Tool credentials", () => {
|
|
520
|
-
it("warns needs-credentials for a tool-using share without bindings",
|
|
521
|
-
|
|
522
|
-
|
|
591
|
+
it("warns needs-credentials for a tool-using share without bindings", () => {
|
|
592
|
+
renderOpenDialog(createMockStigmer(), {
|
|
593
|
+
agent: makeAgent({ mcpUsages: true }),
|
|
594
|
+
share: makeShare({ enabled: true }),
|
|
523
595
|
});
|
|
524
|
-
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
525
596
|
|
|
526
597
|
expect(
|
|
527
598
|
screen.getByText(/no credentials are bound to this share/i),
|
|
@@ -530,12 +601,16 @@ describe("ShareAgentDialog", () => {
|
|
|
530
601
|
|
|
531
602
|
it("binds an org-shared environment by applying the appended refs", async () => {
|
|
532
603
|
const apply = vi.fn().mockResolvedValue({});
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
604
|
+
renderOpenDialog(
|
|
605
|
+
createMockStigmer({
|
|
606
|
+
apply,
|
|
607
|
+
environments: [orgSharedEnv("github-creds", "GitHub Creds")],
|
|
608
|
+
}),
|
|
609
|
+
{
|
|
610
|
+
agent: makeAgent({ mcpUsages: true }),
|
|
611
|
+
share: makeShare({ enabled: true }),
|
|
612
|
+
},
|
|
613
|
+
);
|
|
539
614
|
|
|
540
615
|
// The section is expanded by default for tool-using agents.
|
|
541
616
|
const picker = await screen.findByLabelText("Add environment", {
|
|
@@ -562,11 +637,15 @@ describe("ShareAgentDialog", () => {
|
|
|
562
637
|
},
|
|
563
638
|
spec: { description: "" },
|
|
564
639
|
};
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
640
|
+
renderOpenDialog(
|
|
641
|
+
createMockStigmer({
|
|
642
|
+
environments: [orgSharedEnv("github-creds", "GitHub Creds"), privateEnv],
|
|
643
|
+
}),
|
|
644
|
+
{
|
|
645
|
+
agent: makeAgent({ mcpUsages: true }),
|
|
646
|
+
share: makeShare({ enabled: true }),
|
|
647
|
+
},
|
|
648
|
+
);
|
|
570
649
|
|
|
571
650
|
await screen.findByLabelText("Add environment", { selector: "select" });
|
|
572
651
|
// Private environments are guest-unusable (the runtime merge skips
|
|
@@ -575,24 +654,21 @@ describe("ShareAgentDialog", () => {
|
|
|
575
654
|
expect(screen.queryByText("Personal Creds")).toBeNull();
|
|
576
655
|
});
|
|
577
656
|
|
|
578
|
-
it("hides the section entirely for org-audience shares",
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
),
|
|
657
|
+
it("hides the section entirely for org-audience shares", () => {
|
|
658
|
+
renderOpenDialog(createMockStigmer(), {
|
|
659
|
+
agent: makeAgent({ mcpUsages: true }),
|
|
660
|
+
share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
583
661
|
});
|
|
584
|
-
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
585
662
|
|
|
586
663
|
expect(screen.queryByText("Tool credentials")).toBeNull();
|
|
587
664
|
});
|
|
588
665
|
});
|
|
589
666
|
|
|
590
667
|
describe("Embed tab", () => {
|
|
591
|
-
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>",
|
|
592
|
-
|
|
593
|
-
|
|
668
|
+
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
|
|
669
|
+
renderOpenDialog(createMockStigmer(), {
|
|
670
|
+
share: makeShare({ enabled: true }),
|
|
594
671
|
});
|
|
595
|
-
await renderOpenDialog(client);
|
|
596
672
|
|
|
597
673
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
598
674
|
// The loader must be served from the SAME origin as the share URL —
|
|
@@ -609,11 +685,10 @@ describe("ShareAgentDialog", () => {
|
|
|
609
685
|
).toBeTruthy();
|
|
610
686
|
});
|
|
611
687
|
|
|
612
|
-
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative",
|
|
613
|
-
|
|
614
|
-
|
|
688
|
+
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
|
|
689
|
+
renderOpenDialog(createMockStigmer(), {
|
|
690
|
+
share: makeShare({ enabled: true }),
|
|
615
691
|
});
|
|
616
|
-
await renderOpenDialog(client);
|
|
617
692
|
|
|
618
693
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
619
694
|
// Collapsed by default — the script snippet is the primary path.
|
|
@@ -634,11 +709,9 @@ describe("ShareAgentDialog", () => {
|
|
|
634
709
|
|
|
635
710
|
it("rejects an invalid origin without calling the RPC", async () => {
|
|
636
711
|
const apply = vi.fn();
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
getByAgent: withShare(makeShare({ enabled: true })),
|
|
712
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
713
|
+
share: makeShare({ enabled: true }),
|
|
640
714
|
});
|
|
641
|
-
await renderOpenDialog(client);
|
|
642
715
|
|
|
643
716
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
644
717
|
const input = screen.getByLabelText("Add allowed origin");
|
|
@@ -653,16 +726,12 @@ describe("ShareAgentDialog", () => {
|
|
|
653
726
|
|
|
654
727
|
it("adds a valid origin by applying the appended list", async () => {
|
|
655
728
|
const apply = vi.fn().mockResolvedValue({});
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
allowedOrigins: ["https://existing.example.com"],
|
|
662
|
-
}),
|
|
663
|
-
),
|
|
729
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
730
|
+
share: makeShare({
|
|
731
|
+
enabled: true,
|
|
732
|
+
allowedOrigins: ["https://existing.example.com"],
|
|
733
|
+
}),
|
|
664
734
|
});
|
|
665
|
-
await renderOpenDialog(client);
|
|
666
735
|
|
|
667
736
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
668
737
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -682,16 +751,12 @@ describe("ShareAgentDialog", () => {
|
|
|
682
751
|
|
|
683
752
|
it("rejects a duplicate origin", async () => {
|
|
684
753
|
const apply = vi.fn();
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
allowedOrigins: ["https://example.com"],
|
|
691
|
-
}),
|
|
692
|
-
),
|
|
754
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
755
|
+
share: makeShare({
|
|
756
|
+
enabled: true,
|
|
757
|
+
allowedOrigins: ["https://example.com"],
|
|
758
|
+
}),
|
|
693
759
|
});
|
|
694
|
-
await renderOpenDialog(client);
|
|
695
760
|
|
|
696
761
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
697
762
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -705,16 +770,12 @@ describe("ShareAgentDialog", () => {
|
|
|
705
770
|
|
|
706
771
|
it("removes an origin by applying the filtered list", async () => {
|
|
707
772
|
const apply = vi.fn().mockResolvedValue({});
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
714
|
-
}),
|
|
715
|
-
),
|
|
773
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
774
|
+
share: makeShare({
|
|
775
|
+
enabled: true,
|
|
776
|
+
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
777
|
+
}),
|
|
716
778
|
});
|
|
717
|
-
await renderOpenDialog(client);
|
|
718
779
|
|
|
719
780
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
720
781
|
fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
|
|
@@ -728,16 +789,12 @@ describe("ShareAgentDialog", () => {
|
|
|
728
789
|
describe("visitor messages", () => {
|
|
729
790
|
it("saves edited messages as part of the complete spec", async () => {
|
|
730
791
|
const apply = vi.fn().mockResolvedValue({});
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
allowedOrigins: ["https://example.com"],
|
|
737
|
-
}),
|
|
738
|
-
),
|
|
792
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
793
|
+
share: makeShare({
|
|
794
|
+
enabled: true,
|
|
795
|
+
allowedOrigins: ["https://example.com"],
|
|
796
|
+
}),
|
|
739
797
|
});
|
|
740
|
-
await renderOpenDialog(client);
|
|
741
798
|
|
|
742
799
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
743
800
|
fireEvent.change(screen.getByLabelText(/Rate limited/), {
|
|
@@ -752,11 +809,10 @@ describe("ShareAgentDialog", () => {
|
|
|
752
809
|
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
753
810
|
});
|
|
754
811
|
|
|
755
|
-
it("caps each message at 300 characters",
|
|
756
|
-
|
|
757
|
-
|
|
812
|
+
it("caps each message at 300 characters", () => {
|
|
813
|
+
renderOpenDialog(createMockStigmer(), {
|
|
814
|
+
share: makeShare({ enabled: true }),
|
|
758
815
|
});
|
|
759
|
-
await renderOpenDialog(client);
|
|
760
816
|
|
|
761
817
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
762
818
|
const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
|
|
@@ -770,11 +826,10 @@ describe("ShareAgentDialog", () => {
|
|
|
770
826
|
const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
|
|
771
827
|
balance: { availableMicros: BigInt(12_500_000) },
|
|
772
828
|
});
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
829
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
830
|
+
mode: "cloud",
|
|
831
|
+
share: makeShare({ enabled: true }),
|
|
776
832
|
});
|
|
777
|
-
await renderOpenDialog(client, { mode: "cloud" });
|
|
778
833
|
|
|
779
834
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
780
835
|
await waitFor(() =>
|
|
@@ -783,13 +838,12 @@ describe("ShareAgentDialog", () => {
|
|
|
783
838
|
expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
|
|
784
839
|
});
|
|
785
840
|
|
|
786
|
-
it("degrades to the who-pays line alone in local mode (no billing fetch)",
|
|
841
|
+
it("degrades to the who-pays line alone in local mode (no billing fetch)", () => {
|
|
787
842
|
const getOrCreateBillingAccount = vi.fn();
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
843
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
844
|
+
mode: "local",
|
|
845
|
+
share: makeShare({ enabled: true }),
|
|
791
846
|
});
|
|
792
|
-
await renderOpenDialog(client, { mode: "local" });
|
|
793
847
|
|
|
794
848
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
795
849
|
expect(screen.queryByText(/available/)).toBeNull();
|
|
@@ -800,11 +854,10 @@ describe("ShareAgentDialog", () => {
|
|
|
800
854
|
const getOrCreateBillingAccount = vi
|
|
801
855
|
.fn()
|
|
802
856
|
.mockRejectedValue(new Error("billing unavailable"));
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
857
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
858
|
+
mode: "cloud",
|
|
859
|
+
share: makeShare({ enabled: true }),
|
|
806
860
|
});
|
|
807
|
-
await renderOpenDialog(client, { mode: "cloud" });
|
|
808
861
|
|
|
809
862
|
await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
|
|
810
863
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
@@ -814,11 +867,10 @@ describe("ShareAgentDialog", () => {
|
|
|
814
867
|
});
|
|
815
868
|
|
|
816
869
|
describe("Developer tab", () => {
|
|
817
|
-
it("shows the platform client snippet and docs link",
|
|
818
|
-
|
|
819
|
-
|
|
870
|
+
it("shows the platform client snippet and docs link", () => {
|
|
871
|
+
renderOpenDialog(createMockStigmer(), {
|
|
872
|
+
share: makeShare({ enabled: true }),
|
|
820
873
|
});
|
|
821
|
-
await renderOpenDialog(client);
|
|
822
874
|
|
|
823
875
|
fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
|
|
824
876
|
expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
|
|
@@ -827,24 +879,24 @@ describe("ShareAgentDialog", () => {
|
|
|
827
879
|
});
|
|
828
880
|
});
|
|
829
881
|
|
|
830
|
-
it("renders in-flow without showModal when modal is false",
|
|
882
|
+
it("renders in-flow without showModal when modal is false", () => {
|
|
831
883
|
const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
|
|
832
|
-
|
|
833
|
-
|
|
884
|
+
renderOpenDialog(createMockStigmer(), {
|
|
885
|
+
modal: false,
|
|
886
|
+
share: makeShare({ enabled: true }),
|
|
834
887
|
});
|
|
835
|
-
await renderOpenDialog(client, { modal: false });
|
|
836
888
|
|
|
837
889
|
expect(screen.getByText("Share")).toBeTruthy();
|
|
838
890
|
expect(showModal).not.toHaveBeenCalled();
|
|
839
891
|
showModal.mockRestore();
|
|
840
892
|
});
|
|
841
893
|
|
|
842
|
-
it("requests close via Done and the close affordance",
|
|
894
|
+
it("requests close via Done and the close affordance", () => {
|
|
843
895
|
const onOpenChange = vi.fn();
|
|
844
|
-
|
|
845
|
-
|
|
896
|
+
renderOpenDialog(createMockStigmer(), {
|
|
897
|
+
onOpenChange,
|
|
898
|
+
share: makeShare({ enabled: true }),
|
|
846
899
|
});
|
|
847
|
-
await renderOpenDialog(client, { onOpenChange });
|
|
848
900
|
|
|
849
901
|
screen.getByText("Done").click();
|
|
850
902
|
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
@@ -855,11 +907,10 @@ describe("ShareAgentDialog", () => {
|
|
|
855
907
|
});
|
|
856
908
|
|
|
857
909
|
describe("Reset link (rotatable share token)", () => {
|
|
858
|
-
it("appends the status token to the shown link and embed snippet",
|
|
859
|
-
|
|
860
|
-
|
|
910
|
+
it("appends the status token to the shown link and embed snippet", () => {
|
|
911
|
+
renderOpenDialog(createMockStigmer(), {
|
|
912
|
+
share: makeShare({ enabled: true }, "tok123"),
|
|
861
913
|
});
|
|
862
|
-
await renderOpenDialog(client);
|
|
863
914
|
|
|
864
915
|
expect(
|
|
865
916
|
screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
|
|
@@ -874,11 +925,10 @@ describe("ShareAgentDialog", () => {
|
|
|
874
925
|
.fn()
|
|
875
926
|
.mockResolvedValue(makeShare({ enabled: true }, "fresh-token"));
|
|
876
927
|
const onSharingChanged = vi.fn();
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
928
|
+
renderOpenDialog(createMockStigmer({ rotateShareLink }), {
|
|
929
|
+
onSharingChanged,
|
|
930
|
+
share: makeShare({ enabled: true }),
|
|
880
931
|
});
|
|
881
|
-
await renderOpenDialog(client, { onSharingChanged });
|
|
882
932
|
|
|
883
933
|
// A plain link shows no token before the reset.
|
|
884
934
|
expect(
|
|
@@ -895,23 +945,20 @@ describe("ShareAgentDialog", () => {
|
|
|
895
945
|
).toBeTruthy(),
|
|
896
946
|
);
|
|
897
947
|
expect(rotateShareLink).toHaveBeenCalledTimes(1);
|
|
898
|
-
// The rotation targets the
|
|
948
|
+
// The rotation targets the given share by its own id.
|
|
899
949
|
expect(
|
|
900
950
|
(rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
|
|
901
951
|
).toBe("ash_1");
|
|
902
952
|
expect(onSharingChanged).toHaveBeenCalled();
|
|
903
953
|
});
|
|
904
954
|
|
|
905
|
-
it("hides the Reset control and the token for org-members-only shares",
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
"tok123",
|
|
911
|
-
),
|
|
955
|
+
it("hides the Reset control and the token for org-members-only shares", () => {
|
|
956
|
+
renderOpenDialog(createMockStigmer(), {
|
|
957
|
+
share: makeShare(
|
|
958
|
+
{ enabled: true, audience: AgentShareAudience.org },
|
|
959
|
+
"tok123",
|
|
912
960
|
),
|
|
913
961
|
});
|
|
914
|
-
await renderOpenDialog(client);
|
|
915
962
|
|
|
916
963
|
// Org access is gated by membership, not the link token: the member
|
|
917
964
|
// link stays clean and the Reset lever is not offered.
|