@stigmer/react 3.1.13 → 3.1.15
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 +9 -9
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +23 -31
- package/agent/AgentDetailView.js.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts +4 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.js +12 -5
- package/agent-instance/AgentInstanceEmptyState.js.map +1 -1
- package/agent-instance/AgentInstanceList.d.ts +13 -2
- package/agent-instance/AgentInstanceList.d.ts.map +1 -1
- package/agent-instance/AgentInstanceList.js +8 -4
- package/agent-instance/AgentInstanceList.js.map +1 -1
- package/agent-instance/useAgentInstances.d.ts +10 -3
- package/agent-instance/useAgentInstances.d.ts.map +1 -1
- package/agent-instance/useAgentInstances.js +12 -4
- package/agent-instance/useAgentInstances.js.map +1 -1
- package/button/Button.d.ts +54 -0
- package/button/Button.d.ts.map +1 -0
- package/button/Button.js +42 -0
- package/button/Button.js.map +1 -0
- package/button/index.d.ts +3 -0
- package/button/index.d.ts.map +1 -0
- package/button/index.js +2 -0
- package/button/index.js.map +1 -0
- package/empty-state/EmptyState.d.ts.map +1 -1
- package/empty-state/EmptyState.js +8 -3
- package/empty-state/EmptyState.js.map +1 -1
- package/empty-state/types.d.ts +2 -0
- package/empty-state/types.d.ts.map +1 -1
- package/execution/index.d.ts +1 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +81 -9
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +42 -4
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +5 -3
- package/index.d.ts.map +1 -1
- package/index.js +4 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/session/useNewSessionFlow.d.ts +6 -3
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +34 -35
- package/session/useNewSessionFlow.js.map +1 -1
- package/sharing/AgentShareList.d.ts +46 -0
- package/sharing/AgentShareList.d.ts.map +1 -0
- package/sharing/AgentShareList.js +159 -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 +48 -0
- package/sharing/useAgentShares.d.ts.map +1 -0
- package/sharing/useAgentShares.js +44 -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 +37 -43
- package/src/agent-instance/AgentInstanceEmptyState.tsx +24 -43
- package/src/agent-instance/AgentInstanceList.tsx +29 -16
- package/src/agent-instance/__tests__/agent-instance-hooks.test.tsx +19 -0
- package/src/agent-instance/useAgentInstances.ts +12 -3
- package/src/button/Button.tsx +104 -0
- package/src/button/__tests__/Button.test.tsx +82 -0
- package/src/button/index.ts +2 -0
- package/src/empty-state/EmptyState.tsx +8 -10
- package/src/empty-state/types.ts +2 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +92 -0
- package/src/execution/index.ts +2 -0
- package/src/execution/useCreateAgentExecution.ts +116 -10
- package/src/index.ts +17 -9
- package/src/session/__tests__/useNewSessionFlow.test.tsx +121 -52
- package/src/session/useNewSessionFlow.ts +38 -37
- package/src/sharing/AgentShareList.tsx +502 -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 +186 -0
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/index.ts +10 -12
- package/src/sharing/useAgentShares.ts +74 -0
- package/src/sharing/useCanCreateAgentShare.ts +74 -0
- package/src/sharing/useDeleteAgentShare.ts +73 -0
- package/src/sharing/useSaveAgentShare.ts +73 -23
- package/src/workflow/WorkflowDetailView.tsx +9 -0
- package/src/workflow/instance/WorkflowInstanceEmptyState.tsx +24 -43
- package/src/workflow/instance/WorkflowInstanceList.tsx +29 -16
- package/src/workflow/useWorkflowInstances.ts +10 -3
- package/styles.css +1 -1
- package/workflow/WorkflowDetailView.d.ts +8 -1
- package/workflow/WorkflowDetailView.d.ts.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts +4 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.js +12 -5
- package/workflow/instance/WorkflowInstanceEmptyState.js.map +1 -1
- package/workflow/instance/WorkflowInstanceList.d.ts +13 -2
- package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceList.js +8 -4
- package/workflow/instance/WorkflowInstanceList.js.map +1 -1
- package/workflow/useWorkflowInstances.d.ts +8 -3
- package/workflow/useWorkflowInstances.d.ts.map +1 -1
- package/workflow/useWorkflowInstances.js +10 -4
- package/workflow/useWorkflowInstances.js.map +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
|
@@ -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();
|
|
236
|
+
});
|
|
237
|
+
|
|
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");
|
|
308
251
|
});
|
|
309
252
|
|
|
310
|
-
it("
|
|
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);
|
|
325
268
|
|
|
269
|
+
// The dialog transitions to the editor on the created share.
|
|
326
270
|
await waitFor(() =>
|
|
327
271
|
expect(
|
|
328
|
-
screen.
|
|
329
|
-
).
|
|
272
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
273
|
+
).toBeTruthy(),
|
|
330
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" });
|
|
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.
|
|
349
|
+
await waitFor(() =>
|
|
350
|
+
expect(
|
|
351
|
+
screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
|
|
352
|
+
).toBeTruthy(),
|
|
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,14 +497,22 @@ describe("ShareAgentDialog", () => {
|
|
|
464
497
|
});
|
|
465
498
|
});
|
|
466
499
|
|
|
467
|
-
describe("cross-org mode (
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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 });
|
|
475
516
|
|
|
476
517
|
// The header names whose blueprint this channel serves.
|
|
477
518
|
expect(screen.getByText("acme/support-agent")).toBeTruthy();
|
|
@@ -479,33 +520,20 @@ describe("ShareAgentDialog", () => {
|
|
|
479
520
|
expect(screen.queryByRole("radiogroup", { hidden: true })).toBeNull();
|
|
480
521
|
});
|
|
481
522
|
|
|
482
|
-
it("builds the link
|
|
483
|
-
|
|
484
|
-
const client = createMockStigmer({ apply });
|
|
485
|
-
await renderOpenDialog(client, { shareOrg: "consumer-org" });
|
|
523
|
+
it("builds the link and billing line from the sharing org", () => {
|
|
524
|
+
renderOpenDialog(createMockStigmer(), { share: externalShare });
|
|
486
525
|
|
|
487
|
-
// The hosted URL lives in the sharing org's namespace even before
|
|
488
|
-
// the first save.
|
|
489
526
|
expect(
|
|
490
527
|
screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
|
|
491
528
|
).toBeTruthy();
|
|
492
529
|
// Who-pays names the sharing org, not the agent's.
|
|
493
530
|
expect(screen.getByText("consumer-org")).toBeTruthy();
|
|
494
|
-
|
|
495
|
-
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
496
|
-
|
|
497
|
-
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
498
|
-
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
499
|
-
expect(input.org).toBe("consumer-org");
|
|
500
|
-
expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
|
|
501
|
-
expect(input.audience).toBe(AgentShareAudience.public);
|
|
502
531
|
});
|
|
503
532
|
|
|
504
|
-
it("same-org dialogs are unchanged when
|
|
505
|
-
|
|
506
|
-
|
|
533
|
+
it("same-org dialogs are unchanged when the share org equals the agent's", () => {
|
|
534
|
+
renderOpenDialog(createMockStigmer(), {
|
|
535
|
+
share: makeShare({ enabled: true }),
|
|
507
536
|
});
|
|
508
|
-
await renderOpenDialog(client, { shareOrg: "acme" });
|
|
509
537
|
|
|
510
538
|
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
511
539
|
expect(screen.getByRole("radiogroup", { hidden: true })).toBeTruthy();
|
|
@@ -515,17 +543,14 @@ describe("ShareAgentDialog", () => {
|
|
|
515
543
|
it("enabling applies the complete spec and notifies the host", async () => {
|
|
516
544
|
const apply = vi.fn().mockResolvedValue({});
|
|
517
545
|
const onSharingChanged = vi.fn();
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
}),
|
|
526
|
-
),
|
|
546
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
547
|
+
onSharingChanged,
|
|
548
|
+
share: makeShare({
|
|
549
|
+
enabled: false,
|
|
550
|
+
allowedOrigins: ["https://example.com"],
|
|
551
|
+
messages: { rateLimited: "Easy there." },
|
|
552
|
+
}),
|
|
527
553
|
});
|
|
528
|
-
await renderOpenDialog(client, { onSharingChanged });
|
|
529
554
|
|
|
530
555
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
531
556
|
|
|
@@ -547,11 +572,9 @@ describe("ShareAgentDialog", () => {
|
|
|
547
572
|
allowedOrigins: ["https://normalized.example.com"],
|
|
548
573
|
}),
|
|
549
574
|
);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
getByAgent: withShare(makeShare({ enabled: false })),
|
|
575
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
576
|
+
share: makeShare({ enabled: false }),
|
|
553
577
|
});
|
|
554
|
-
await renderOpenDialog(client);
|
|
555
578
|
|
|
556
579
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
557
580
|
await waitFor(() =>
|
|
@@ -565,11 +588,11 @@ describe("ShareAgentDialog", () => {
|
|
|
565
588
|
});
|
|
566
589
|
|
|
567
590
|
describe("Tool credentials", () => {
|
|
568
|
-
it("warns needs-credentials for a tool-using share without bindings",
|
|
569
|
-
|
|
570
|
-
|
|
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 }),
|
|
571
595
|
});
|
|
572
|
-
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
573
596
|
|
|
574
597
|
expect(
|
|
575
598
|
screen.getByText(/no credentials are bound to this share/i),
|
|
@@ -578,12 +601,16 @@ describe("ShareAgentDialog", () => {
|
|
|
578
601
|
|
|
579
602
|
it("binds an org-shared environment by applying the appended refs", async () => {
|
|
580
603
|
const apply = vi.fn().mockResolvedValue({});
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
+
);
|
|
587
614
|
|
|
588
615
|
// The section is expanded by default for tool-using agents.
|
|
589
616
|
const picker = await screen.findByLabelText("Add environment", {
|
|
@@ -610,11 +637,15 @@ describe("ShareAgentDialog", () => {
|
|
|
610
637
|
},
|
|
611
638
|
spec: { description: "" },
|
|
612
639
|
};
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
+
);
|
|
618
649
|
|
|
619
650
|
await screen.findByLabelText("Add environment", { selector: "select" });
|
|
620
651
|
// Private environments are guest-unusable (the runtime merge skips
|
|
@@ -623,24 +654,21 @@ describe("ShareAgentDialog", () => {
|
|
|
623
654
|
expect(screen.queryByText("Personal Creds")).toBeNull();
|
|
624
655
|
});
|
|
625
656
|
|
|
626
|
-
it("hides the section entirely for org-audience shares",
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
),
|
|
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 }),
|
|
631
661
|
});
|
|
632
|
-
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
633
662
|
|
|
634
663
|
expect(screen.queryByText("Tool credentials")).toBeNull();
|
|
635
664
|
});
|
|
636
665
|
});
|
|
637
666
|
|
|
638
667
|
describe("Embed tab", () => {
|
|
639
|
-
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>",
|
|
640
|
-
|
|
641
|
-
|
|
668
|
+
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
|
|
669
|
+
renderOpenDialog(createMockStigmer(), {
|
|
670
|
+
share: makeShare({ enabled: true }),
|
|
642
671
|
});
|
|
643
|
-
await renderOpenDialog(client);
|
|
644
672
|
|
|
645
673
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
646
674
|
// The loader must be served from the SAME origin as the share URL —
|
|
@@ -657,11 +685,10 @@ describe("ShareAgentDialog", () => {
|
|
|
657
685
|
).toBeTruthy();
|
|
658
686
|
});
|
|
659
687
|
|
|
660
|
-
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative",
|
|
661
|
-
|
|
662
|
-
|
|
688
|
+
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
|
|
689
|
+
renderOpenDialog(createMockStigmer(), {
|
|
690
|
+
share: makeShare({ enabled: true }),
|
|
663
691
|
});
|
|
664
|
-
await renderOpenDialog(client);
|
|
665
692
|
|
|
666
693
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
667
694
|
// Collapsed by default — the script snippet is the primary path.
|
|
@@ -682,11 +709,9 @@ describe("ShareAgentDialog", () => {
|
|
|
682
709
|
|
|
683
710
|
it("rejects an invalid origin without calling the RPC", async () => {
|
|
684
711
|
const apply = vi.fn();
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
getByAgent: withShare(makeShare({ enabled: true })),
|
|
712
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
713
|
+
share: makeShare({ enabled: true }),
|
|
688
714
|
});
|
|
689
|
-
await renderOpenDialog(client);
|
|
690
715
|
|
|
691
716
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
692
717
|
const input = screen.getByLabelText("Add allowed origin");
|
|
@@ -701,16 +726,12 @@ describe("ShareAgentDialog", () => {
|
|
|
701
726
|
|
|
702
727
|
it("adds a valid origin by applying the appended list", async () => {
|
|
703
728
|
const apply = vi.fn().mockResolvedValue({});
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
allowedOrigins: ["https://existing.example.com"],
|
|
710
|
-
}),
|
|
711
|
-
),
|
|
729
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
730
|
+
share: makeShare({
|
|
731
|
+
enabled: true,
|
|
732
|
+
allowedOrigins: ["https://existing.example.com"],
|
|
733
|
+
}),
|
|
712
734
|
});
|
|
713
|
-
await renderOpenDialog(client);
|
|
714
735
|
|
|
715
736
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
716
737
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -730,16 +751,12 @@ describe("ShareAgentDialog", () => {
|
|
|
730
751
|
|
|
731
752
|
it("rejects a duplicate origin", async () => {
|
|
732
753
|
const apply = vi.fn();
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
allowedOrigins: ["https://example.com"],
|
|
739
|
-
}),
|
|
740
|
-
),
|
|
754
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
755
|
+
share: makeShare({
|
|
756
|
+
enabled: true,
|
|
757
|
+
allowedOrigins: ["https://example.com"],
|
|
758
|
+
}),
|
|
741
759
|
});
|
|
742
|
-
await renderOpenDialog(client);
|
|
743
760
|
|
|
744
761
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
745
762
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -753,16 +770,12 @@ describe("ShareAgentDialog", () => {
|
|
|
753
770
|
|
|
754
771
|
it("removes an origin by applying the filtered list", async () => {
|
|
755
772
|
const apply = vi.fn().mockResolvedValue({});
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
762
|
-
}),
|
|
763
|
-
),
|
|
773
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
774
|
+
share: makeShare({
|
|
775
|
+
enabled: true,
|
|
776
|
+
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
777
|
+
}),
|
|
764
778
|
});
|
|
765
|
-
await renderOpenDialog(client);
|
|
766
779
|
|
|
767
780
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
768
781
|
fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
|
|
@@ -776,16 +789,12 @@ describe("ShareAgentDialog", () => {
|
|
|
776
789
|
describe("visitor messages", () => {
|
|
777
790
|
it("saves edited messages as part of the complete spec", async () => {
|
|
778
791
|
const apply = vi.fn().mockResolvedValue({});
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
allowedOrigins: ["https://example.com"],
|
|
785
|
-
}),
|
|
786
|
-
),
|
|
792
|
+
renderOpenDialog(createMockStigmer({ apply }), {
|
|
793
|
+
share: makeShare({
|
|
794
|
+
enabled: true,
|
|
795
|
+
allowedOrigins: ["https://example.com"],
|
|
796
|
+
}),
|
|
787
797
|
});
|
|
788
|
-
await renderOpenDialog(client);
|
|
789
798
|
|
|
790
799
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
791
800
|
fireEvent.change(screen.getByLabelText(/Rate limited/), {
|
|
@@ -800,11 +809,10 @@ describe("ShareAgentDialog", () => {
|
|
|
800
809
|
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
801
810
|
});
|
|
802
811
|
|
|
803
|
-
it("caps each message at 300 characters",
|
|
804
|
-
|
|
805
|
-
|
|
812
|
+
it("caps each message at 300 characters", () => {
|
|
813
|
+
renderOpenDialog(createMockStigmer(), {
|
|
814
|
+
share: makeShare({ enabled: true }),
|
|
806
815
|
});
|
|
807
|
-
await renderOpenDialog(client);
|
|
808
816
|
|
|
809
817
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
810
818
|
const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
|
|
@@ -818,11 +826,10 @@ describe("ShareAgentDialog", () => {
|
|
|
818
826
|
const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
|
|
819
827
|
balance: { availableMicros: BigInt(12_500_000) },
|
|
820
828
|
});
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
829
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
830
|
+
mode: "cloud",
|
|
831
|
+
share: makeShare({ enabled: true }),
|
|
824
832
|
});
|
|
825
|
-
await renderOpenDialog(client, { mode: "cloud" });
|
|
826
833
|
|
|
827
834
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
828
835
|
await waitFor(() =>
|
|
@@ -831,13 +838,12 @@ describe("ShareAgentDialog", () => {
|
|
|
831
838
|
expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
|
|
832
839
|
});
|
|
833
840
|
|
|
834
|
-
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)", () => {
|
|
835
842
|
const getOrCreateBillingAccount = vi.fn();
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
843
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
844
|
+
mode: "local",
|
|
845
|
+
share: makeShare({ enabled: true }),
|
|
839
846
|
});
|
|
840
|
-
await renderOpenDialog(client, { mode: "local" });
|
|
841
847
|
|
|
842
848
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
843
849
|
expect(screen.queryByText(/available/)).toBeNull();
|
|
@@ -848,11 +854,10 @@ describe("ShareAgentDialog", () => {
|
|
|
848
854
|
const getOrCreateBillingAccount = vi
|
|
849
855
|
.fn()
|
|
850
856
|
.mockRejectedValue(new Error("billing unavailable"));
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
857
|
+
renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
|
|
858
|
+
mode: "cloud",
|
|
859
|
+
share: makeShare({ enabled: true }),
|
|
854
860
|
});
|
|
855
|
-
await renderOpenDialog(client, { mode: "cloud" });
|
|
856
861
|
|
|
857
862
|
await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
|
|
858
863
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
@@ -862,11 +867,10 @@ describe("ShareAgentDialog", () => {
|
|
|
862
867
|
});
|
|
863
868
|
|
|
864
869
|
describe("Developer tab", () => {
|
|
865
|
-
it("shows the platform client snippet and docs link",
|
|
866
|
-
|
|
867
|
-
|
|
870
|
+
it("shows the platform client snippet and docs link", () => {
|
|
871
|
+
renderOpenDialog(createMockStigmer(), {
|
|
872
|
+
share: makeShare({ enabled: true }),
|
|
868
873
|
});
|
|
869
|
-
await renderOpenDialog(client);
|
|
870
874
|
|
|
871
875
|
fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
|
|
872
876
|
expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
|
|
@@ -875,24 +879,24 @@ describe("ShareAgentDialog", () => {
|
|
|
875
879
|
});
|
|
876
880
|
});
|
|
877
881
|
|
|
878
|
-
it("renders in-flow without showModal when modal is false",
|
|
882
|
+
it("renders in-flow without showModal when modal is false", () => {
|
|
879
883
|
const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
|
|
880
|
-
|
|
881
|
-
|
|
884
|
+
renderOpenDialog(createMockStigmer(), {
|
|
885
|
+
modal: false,
|
|
886
|
+
share: makeShare({ enabled: true }),
|
|
882
887
|
});
|
|
883
|
-
await renderOpenDialog(client, { modal: false });
|
|
884
888
|
|
|
885
889
|
expect(screen.getByText("Share")).toBeTruthy();
|
|
886
890
|
expect(showModal).not.toHaveBeenCalled();
|
|
887
891
|
showModal.mockRestore();
|
|
888
892
|
});
|
|
889
893
|
|
|
890
|
-
it("requests close via Done and the close affordance",
|
|
894
|
+
it("requests close via Done and the close affordance", () => {
|
|
891
895
|
const onOpenChange = vi.fn();
|
|
892
|
-
|
|
893
|
-
|
|
896
|
+
renderOpenDialog(createMockStigmer(), {
|
|
897
|
+
onOpenChange,
|
|
898
|
+
share: makeShare({ enabled: true }),
|
|
894
899
|
});
|
|
895
|
-
await renderOpenDialog(client, { onOpenChange });
|
|
896
900
|
|
|
897
901
|
screen.getByText("Done").click();
|
|
898
902
|
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
@@ -903,11 +907,10 @@ describe("ShareAgentDialog", () => {
|
|
|
903
907
|
});
|
|
904
908
|
|
|
905
909
|
describe("Reset link (rotatable share token)", () => {
|
|
906
|
-
it("appends the status token to the shown link and embed snippet",
|
|
907
|
-
|
|
908
|
-
|
|
910
|
+
it("appends the status token to the shown link and embed snippet", () => {
|
|
911
|
+
renderOpenDialog(createMockStigmer(), {
|
|
912
|
+
share: makeShare({ enabled: true }, "tok123"),
|
|
909
913
|
});
|
|
910
|
-
await renderOpenDialog(client);
|
|
911
914
|
|
|
912
915
|
expect(
|
|
913
916
|
screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
|
|
@@ -922,11 +925,10 @@ describe("ShareAgentDialog", () => {
|
|
|
922
925
|
.fn()
|
|
923
926
|
.mockResolvedValue(makeShare({ enabled: true }, "fresh-token"));
|
|
924
927
|
const onSharingChanged = vi.fn();
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
+
renderOpenDialog(createMockStigmer({ rotateShareLink }), {
|
|
929
|
+
onSharingChanged,
|
|
930
|
+
share: makeShare({ enabled: true }),
|
|
928
931
|
});
|
|
929
|
-
await renderOpenDialog(client, { onSharingChanged });
|
|
930
932
|
|
|
931
933
|
// A plain link shows no token before the reset.
|
|
932
934
|
expect(
|
|
@@ -943,23 +945,20 @@ describe("ShareAgentDialog", () => {
|
|
|
943
945
|
).toBeTruthy(),
|
|
944
946
|
);
|
|
945
947
|
expect(rotateShareLink).toHaveBeenCalledTimes(1);
|
|
946
|
-
// The rotation targets the
|
|
948
|
+
// The rotation targets the given share by its own id.
|
|
947
949
|
expect(
|
|
948
950
|
(rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
|
|
949
951
|
).toBe("ash_1");
|
|
950
952
|
expect(onSharingChanged).toHaveBeenCalled();
|
|
951
953
|
});
|
|
952
954
|
|
|
953
|
-
it("hides the Reset control and the token for org-members-only shares",
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
"tok123",
|
|
959
|
-
),
|
|
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",
|
|
960
960
|
),
|
|
961
961
|
});
|
|
962
|
-
await renderOpenDialog(client);
|
|
963
962
|
|
|
964
963
|
// Org access is gated by membership, not the link token: the member
|
|
965
964
|
// link stays clean and the Reset lever is not offered.
|