@stigmer/react 3.1.6 → 3.1.8
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 -1
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +15 -4
- package/agent/AgentDetailView.js.map +1 -1
- package/environment/EnvironmentListPanel.d.ts.map +1 -1
- package/environment/EnvironmentListPanel.js +11 -3
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/EnvironmentVariableEditor.d.ts.map +1 -1
- package/environment/EnvironmentVariableEditor.js +9 -3
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/environment/index.d.ts +1 -0
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +1 -0
- package/environment/index.js.map +1 -1
- package/environment/shareRestriction.d.ts +13 -0
- package/environment/shareRestriction.d.ts.map +1 -0
- package/environment/shareRestriction.js +15 -0
- package/environment/shareRestriction.js.map +1 -0
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +12 -0
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/ToolRunGroup.js +4 -0
- package/execution/ToolRunGroup.js.map +1 -1
- package/execution/WriteBackCard.d.ts +14 -5
- package/execution/WriteBackCard.d.ts.map +1 -1
- package/execution/WriteBackCard.js +103 -32
- package/execution/WriteBackCard.js.map +1 -1
- package/execution/WriteBacksWidget.d.ts +1 -1
- package/execution/WriteBacksWidget.js +2 -2
- package/execution/WriteBacksWidget.js.map +1 -1
- package/execution/index.d.ts +2 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -0
- package/execution/index.js.map +1 -1
- package/execution/write-back-utils.d.ts +57 -0
- package/execution/write-back-utils.d.ts.map +1 -0
- package/execution/write-back-utils.js +81 -0
- package/execution/write-back-utils.js.map +1 -0
- package/index.d.ts +6 -2
- package/index.d.ts.map +1 -1
- package/index.js +6 -1
- package/index.js.map +1 -1
- package/internal/dev/dom-counter.d.ts.map +1 -1
- package/internal/dev/dom-counter.js +2 -2
- package/internal/dev/dom-counter.js.map +1 -1
- package/internal/dev/enabled.d.ts +7 -0
- package/internal/dev/enabled.d.ts.map +1 -0
- package/internal/dev/enabled.js +38 -0
- package/internal/dev/enabled.js.map +1 -0
- package/internal/dev/profiler-wrapper.d.ts +3 -3
- package/internal/dev/profiler-wrapper.d.ts.map +1 -1
- package/internal/dev/profiler-wrapper.js +5 -5
- package/internal/dev/profiler-wrapper.js.map +1 -1
- package/internal/dev/use-key-stability.d.ts.map +1 -1
- package/internal/dev/use-key-stability.js +2 -2
- package/internal/dev/use-key-stability.js.map +1 -1
- package/internal/dev/use-render-tracer.d.ts +2 -2
- package/internal/dev/use-render-tracer.d.ts.map +1 -1
- package/internal/dev/use-render-tracer.js +4 -4
- package/internal/dev/use-render-tracer.js.map +1 -1
- package/internal/dev/use-stream-rate.d.ts +2 -1
- package/internal/dev/use-stream-rate.d.ts.map +1 -1
- package/internal/dev/use-stream-rate.js +4 -3
- package/internal/dev/use-stream-rate.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +3 -0
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +22 -8
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/useUpdateVisibility.d.ts +5 -3
- package/library/useUpdateVisibility.d.ts.map +1 -1
- package/library/useUpdateVisibility.js +7 -2
- package/library/useUpdateVisibility.js.map +1 -1
- package/library/visibilityLevels.d.ts +17 -0
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +32 -0
- package/library/visibilityLevels.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.d.ts.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +7 -3
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +6 -3
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.d.ts.map +1 -1
- package/mcp-server/McpServerPicker.js +3 -1
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/OAuthRequiredNotice.d.ts +29 -0
- package/mcp-server/OAuthRequiredNotice.d.ts.map +1 -0
- package/mcp-server/OAuthRequiredNotice.js +29 -0
- package/mcp-server/OAuthRequiredNotice.js.map +1 -0
- package/mcp-server/StdioSandboxNotice.d.ts +43 -0
- package/mcp-server/StdioSandboxNotice.d.ts.map +1 -0
- package/mcp-server/StdioSandboxNotice.js +45 -0
- package/mcp-server/StdioSandboxNotice.js.map +1 -0
- package/mcp-server/useMcpServerCredentials.d.ts +12 -0
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +12 -4
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/package.json +4 -4
- package/session/NewSessionViewer.d.ts +11 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +28 -8
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +9 -2
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +26 -12
- package/session/SessionViewer.js.map +1 -1
- package/session/audience.d.ts +12 -4
- package/session/audience.d.ts.map +1 -1
- package/session/facets/ChangesTab.d.ts.map +1 -1
- package/session/facets/ChangesTab.js +3 -1
- package/session/facets/ChangesTab.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +12 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +13 -1
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +13 -0
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +7 -2
- package/session/useSessionPageFlow.js.map +1 -1
- package/sharing/ShareAgentDialog.d.ts +55 -0
- package/sharing/ShareAgentDialog.d.ts.map +1 -0
- package/sharing/ShareAgentDialog.js +393 -0
- package/sharing/ShareAgentDialog.js.map +1 -0
- package/sharing/SharedAgentChat.d.ts +84 -0
- package/sharing/SharedAgentChat.d.ts.map +1 -0
- package/sharing/SharedAgentChat.js +80 -0
- package/sharing/SharedAgentChat.js.map +1 -0
- package/sharing/index.d.ts +16 -0
- package/sharing/index.d.ts.map +1 -0
- package/sharing/index.js +11 -0
- package/sharing/index.js.map +1 -0
- package/sharing/useRotateShareLink.d.ts +42 -0
- package/sharing/useRotateShareLink.d.ts.map +1 -0
- package/sharing/useRotateShareLink.js +55 -0
- package/sharing/useRotateShareLink.js.map +1 -0
- package/sharing/useShareAgent.d.ts +68 -0
- package/sharing/useShareAgent.d.ts.map +1 -0
- package/sharing/useShareAgent.js +41 -0
- package/sharing/useShareAgent.js.map +1 -0
- package/sharing/useShareToolReadiness.d.ts +48 -0
- package/sharing/useShareToolReadiness.d.ts.map +1 -0
- package/sharing/useShareToolReadiness.js +93 -0
- package/sharing/useShareToolReadiness.js.map +1 -0
- package/sharing/useSharedAgentProfile.d.ts +84 -0
- package/sharing/useSharedAgentProfile.d.ts.map +1 -0
- package/sharing/useSharedAgentProfile.js +72 -0
- package/sharing/useSharedAgentProfile.js.map +1 -0
- package/sharing/useUpdateAgentSharing.d.ts +86 -0
- package/sharing/useUpdateAgentSharing.d.ts.map +1 -0
- package/sharing/useUpdateAgentSharing.js +81 -0
- package/sharing/useUpdateAgentSharing.js.map +1 -0
- package/src/agent/AgentDetailView.tsx +28 -3
- package/src/environment/EnvironmentListPanel.tsx +47 -18
- package/src/environment/EnvironmentVariableEditor.tsx +27 -17
- package/src/environment/__tests__/shareRestriction.test.ts +35 -0
- package/src/environment/index.ts +5 -0
- package/src/environment/shareRestriction.ts +22 -0
- package/src/execution/ToolCallItem.tsx +19 -1
- package/src/execution/ToolRunGroup.tsx +4 -0
- package/src/execution/WriteBackCard.tsx +285 -88
- package/src/execution/WriteBacksWidget.tsx +5 -5
- package/src/execution/__tests__/ToolCallItem.test.tsx +51 -0
- package/src/execution/__tests__/ToolRunGroup.test.tsx +22 -0
- package/src/execution/__tests__/WriteBackCard.test.tsx +64 -10
- package/src/execution/__tests__/write-back-utils.test.ts +135 -0
- package/src/execution/index.ts +7 -0
- package/src/execution/write-back-utils.ts +107 -0
- package/src/index.ts +34 -0
- package/src/internal/dev/__tests__/use-key-stability.test.ts +4 -0
- package/src/internal/dev/__tests__/use-render-tracer.test.ts +4 -0
- package/src/internal/dev/dom-counter.ts +2 -3
- package/src/internal/dev/enabled.ts +39 -0
- package/src/internal/dev/profiler-wrapper.tsx +5 -6
- package/src/internal/dev/use-key-stability.ts +2 -3
- package/src/internal/dev/use-render-tracer.ts +4 -5
- package/src/internal/dev/use-stream-rate.ts +4 -4
- package/src/library/ResourceVisibilityControl.tsx +23 -7
- package/src/library/__tests__/visibilityLevels.test.ts +33 -0
- package/src/library/useUpdateVisibility.ts +9 -3
- package/src/library/visibilityLevels.ts +36 -0
- package/src/mcp-server/McpServerConnectDialog.tsx +23 -11
- package/src/mcp-server/McpServerDetailView.tsx +22 -9
- package/src/mcp-server/McpServerPicker.tsx +12 -9
- package/src/mcp-server/OAuthRequiredNotice.tsx +77 -0
- package/src/mcp-server/StdioSandboxNotice.tsx +98 -0
- package/src/mcp-server/__tests__/OAuthRequiredNotice.test.tsx +25 -0
- package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +71 -0
- package/src/mcp-server/useMcpServerCredentials.ts +25 -4
- package/src/session/NewSessionViewer.tsx +67 -31
- package/src/session/SessionViewer.tsx +67 -36
- package/src/session/__tests__/audienceWiring.test.tsx +99 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +75 -1
- package/src/session/__tests__/useSessionPageFlow.guest.test.tsx +112 -0
- package/src/session/audience.ts +12 -4
- package/src/session/facets/ChangesTab.tsx +6 -4
- package/src/session/useNewSessionFlow.ts +26 -1
- package/src/session/useSessionPageFlow.ts +24 -2
- package/src/sharing/ShareAgentDialog.tsx +1255 -0
- package/src/sharing/SharedAgentChat.tsx +283 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +745 -0
- package/src/sharing/__tests__/SharedAgentChat.test.tsx +222 -0
- package/src/sharing/__tests__/useRotateShareLink.test.tsx +96 -0
- package/src/sharing/__tests__/useShareAgent.test.tsx +127 -0
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +182 -0
- package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +201 -0
- package/src/sharing/__tests__/useUpdateAgentSharing.test.tsx +200 -0
- package/src/sharing/index.ts +30 -0
- package/src/sharing/useRotateShareLink.ts +76 -0
- package/src/sharing/useShareAgent.tsx +106 -0
- package/src/sharing/useShareToolReadiness.ts +129 -0
- package/src/sharing/useSharedAgentProfile.ts +131 -0
- package/src/sharing/useUpdateAgentSharing.ts +151 -0
- package/src/switch/Switch.tsx +92 -0
- package/src/switch/__tests__/Switch.test.tsx +88 -0
- package/src/switch/index.ts +2 -0
- package/styles.css +1 -1
- package/switch/Switch.d.ts +47 -0
- package/switch/Switch.d.ts.map +1 -0
- package/switch/Switch.js +36 -0
- package/switch/Switch.js.map +1 -0
- package/switch/index.d.ts +3 -0
- package/switch/index.d.ts.map +1 -0
- package/switch/index.js +2 -0
- package/switch/index.js.map +1 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { UpdateAgentSharingInput } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/io_pb";
|
|
5
|
+
import { AgentSharingAudience } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
6
|
+
import { StigmerContext } from "../../context";
|
|
7
|
+
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
8
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
9
|
+
import { ShareAgentDialog } from "../ShareAgentDialog";
|
|
10
|
+
|
|
11
|
+
// Toasts are visual feedback owned by the feedback module; keep them inert.
|
|
12
|
+
vi.mock("../../feedback/toast", () => ({
|
|
13
|
+
toast: { success: vi.fn(), error: vi.fn() },
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// happy-dom does not implement the native dialog show/close methods.
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
HTMLDialogElement.prototype.showModal = function showModal() {
|
|
19
|
+
this.open = true;
|
|
20
|
+
};
|
|
21
|
+
HTMLDialogElement.prototype.close = function close() {
|
|
22
|
+
this.open = false;
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(cleanup);
|
|
27
|
+
|
|
28
|
+
interface MockOverrides {
|
|
29
|
+
updateSharing?: (input: UpdateAgentSharingInput) => Promise<unknown>;
|
|
30
|
+
rotateShareLink?: (input: unknown) => Promise<unknown>;
|
|
31
|
+
getOrCreateBillingAccount?: (orgId: string) => Promise<unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createMockStigmer(overrides: MockOverrides = {}) {
|
|
35
|
+
return {
|
|
36
|
+
agent: {
|
|
37
|
+
updateSharing: overrides.updateSharing ?? vi.fn().mockResolvedValue({}),
|
|
38
|
+
rotateShareLink:
|
|
39
|
+
overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
|
|
40
|
+
},
|
|
41
|
+
billing: {
|
|
42
|
+
getOrCreateBillingAccount:
|
|
43
|
+
overrides.getOrCreateBillingAccount ??
|
|
44
|
+
vi.fn().mockResolvedValue(null),
|
|
45
|
+
},
|
|
46
|
+
} as never;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Providers({
|
|
50
|
+
client,
|
|
51
|
+
mode = "cloud",
|
|
52
|
+
children,
|
|
53
|
+
}: {
|
|
54
|
+
client: unknown;
|
|
55
|
+
mode?: "cloud" | "local";
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<FetchCacheContext.Provider value={null}>
|
|
60
|
+
<DeploymentModeContext.Provider value={mode}>
|
|
61
|
+
<StigmerContext.Provider value={client as never}>
|
|
62
|
+
{children}
|
|
63
|
+
</StigmerContext.Provider>
|
|
64
|
+
</DeploymentModeContext.Provider>
|
|
65
|
+
</FetchCacheContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeAgent(
|
|
70
|
+
sharing?: {
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
audience?: AgentSharingAudience;
|
|
73
|
+
allowedOrigins?: string[];
|
|
74
|
+
messages?: {
|
|
75
|
+
rateLimited?: string;
|
|
76
|
+
unavailable?: string;
|
|
77
|
+
conversationEnded?: string;
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
shareLinkToken?: string,
|
|
81
|
+
) {
|
|
82
|
+
return {
|
|
83
|
+
metadata: {
|
|
84
|
+
id: "agt_1",
|
|
85
|
+
org: "acme",
|
|
86
|
+
slug: "support-agent",
|
|
87
|
+
name: "Support Agent",
|
|
88
|
+
},
|
|
89
|
+
spec: { sharing },
|
|
90
|
+
...(shareLinkToken !== undefined
|
|
91
|
+
? { status: { shareLinkToken } }
|
|
92
|
+
: {}),
|
|
93
|
+
} as never;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const buildShareUrl = (org: string, slug: string) =>
|
|
97
|
+
`https://app.example.com/chat/${org}/${slug}`;
|
|
98
|
+
|
|
99
|
+
describe("ShareAgentDialog", () => {
|
|
100
|
+
it("mounts no body while closed (billing fetch stays lazy)", () => {
|
|
101
|
+
const getOrCreateBillingAccount = vi.fn();
|
|
102
|
+
render(
|
|
103
|
+
<Providers client={createMockStigmer({ getOrCreateBillingAccount })}>
|
|
104
|
+
<ShareAgentDialog
|
|
105
|
+
open={false}
|
|
106
|
+
onOpenChange={() => {}}
|
|
107
|
+
agent={makeAgent()}
|
|
108
|
+
buildShareUrl={buildShareUrl}
|
|
109
|
+
/>
|
|
110
|
+
</Providers>,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(screen.queryByText("Share")).toBeNull();
|
|
114
|
+
expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders header, toggle, and the share link from buildShareUrl", () => {
|
|
118
|
+
render(
|
|
119
|
+
<Providers client={createMockStigmer()}>
|
|
120
|
+
<ShareAgentDialog
|
|
121
|
+
open
|
|
122
|
+
onOpenChange={() => {}}
|
|
123
|
+
agent={makeAgent({ enabled: true })}
|
|
124
|
+
buildShareUrl={buildShareUrl}
|
|
125
|
+
/>
|
|
126
|
+
</Providers>,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(screen.getByText("Share")).toBeTruthy();
|
|
130
|
+
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
131
|
+
expect(screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked")).toBe("true");
|
|
132
|
+
expect(
|
|
133
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
134
|
+
).toBeTruthy();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("falls back to the relative /chat path when buildShareUrl is omitted", () => {
|
|
138
|
+
render(
|
|
139
|
+
<Providers client={createMockStigmer()}>
|
|
140
|
+
<ShareAgentDialog
|
|
141
|
+
open
|
|
142
|
+
onOpenChange={() => {}}
|
|
143
|
+
agent={makeAgent({ enabled: true })}
|
|
144
|
+
/>
|
|
145
|
+
</Providers>,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("shows the indexability warning", () => {
|
|
152
|
+
render(
|
|
153
|
+
<Providers client={createMockStigmer()}>
|
|
154
|
+
<ShareAgentDialog
|
|
155
|
+
open
|
|
156
|
+
onOpenChange={() => {}}
|
|
157
|
+
agent={makeAgent({ enabled: true })}
|
|
158
|
+
buildShareUrl={buildShareUrl}
|
|
159
|
+
/>
|
|
160
|
+
</Providers>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(
|
|
164
|
+
screen.getByText(/forwarded and indexed by search engines/),
|
|
165
|
+
).toBeTruthy();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("Audience", () => {
|
|
169
|
+
it("defaults to Public link and switching to Org members commits the full block", async () => {
|
|
170
|
+
const updateSharing = vi.fn().mockResolvedValue({});
|
|
171
|
+
render(
|
|
172
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
173
|
+
<ShareAgentDialog
|
|
174
|
+
open
|
|
175
|
+
onOpenChange={() => {}}
|
|
176
|
+
agent={makeAgent({
|
|
177
|
+
enabled: true,
|
|
178
|
+
allowedOrigins: ["https://example.com"],
|
|
179
|
+
messages: { rateLimited: "Easy there." },
|
|
180
|
+
})}
|
|
181
|
+
buildShareUrl={buildShareUrl}
|
|
182
|
+
/>
|
|
183
|
+
</Providers>,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const publicOption = screen.getByRole("radio", {
|
|
187
|
+
name: "Public link",
|
|
188
|
+
hidden: true,
|
|
189
|
+
});
|
|
190
|
+
expect(publicOption.getAttribute("aria-checked")).toBe("true");
|
|
191
|
+
|
|
192
|
+
fireEvent.click(
|
|
193
|
+
screen.getByRole("radio", { name: "Org members", hidden: true }),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
|
|
197
|
+
const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
|
|
198
|
+
expect(input.sharing?.audience).toBe(AgentSharingAudience.org);
|
|
199
|
+
// Whole-block replace: the audience switch must not clobber the rest.
|
|
200
|
+
expect(input.sharing?.enabled).toBe(true);
|
|
201
|
+
expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
|
|
202
|
+
expect(input.sharing?.messages?.rateLimited).toBe("Easy there.");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("renders org-audience copy: member link, revocation note, no indexability warning", () => {
|
|
206
|
+
render(
|
|
207
|
+
<Providers client={createMockStigmer()}>
|
|
208
|
+
<ShareAgentDialog
|
|
209
|
+
open
|
|
210
|
+
onOpenChange={() => {}}
|
|
211
|
+
agent={makeAgent({
|
|
212
|
+
enabled: true,
|
|
213
|
+
audience: AgentSharingAudience.org,
|
|
214
|
+
})}
|
|
215
|
+
buildShareUrl={buildShareUrl}
|
|
216
|
+
/>
|
|
217
|
+
</Providers>,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(screen.getByText("Organization members can chat")).toBeTruthy();
|
|
221
|
+
expect(screen.getByText("Member chat link")).toBeTruthy();
|
|
222
|
+
expect(
|
|
223
|
+
screen.getByText(/access is checked on every message/i),
|
|
224
|
+
).toBeTruthy();
|
|
225
|
+
expect(
|
|
226
|
+
screen.queryByText(/forwarded and indexed by search engines/),
|
|
227
|
+
).toBeNull();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("replaces the Embed tab with a public-only explanation for the org audience", () => {
|
|
231
|
+
render(
|
|
232
|
+
<Providers client={createMockStigmer()}>
|
|
233
|
+
<ShareAgentDialog
|
|
234
|
+
open
|
|
235
|
+
onOpenChange={() => {}}
|
|
236
|
+
agent={makeAgent({
|
|
237
|
+
enabled: true,
|
|
238
|
+
audience: AgentSharingAudience.org,
|
|
239
|
+
})}
|
|
240
|
+
buildShareUrl={buildShareUrl}
|
|
241
|
+
/>
|
|
242
|
+
</Providers>,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
246
|
+
expect(
|
|
247
|
+
screen.getByText(/Embedding isn't available for org-members-only sharing/),
|
|
248
|
+
).toBeTruthy();
|
|
249
|
+
expect(screen.queryByText(/<stigmer-agent/)).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("enabling sends the complete sharing block and notifies the host", async () => {
|
|
254
|
+
const updateSharing = vi.fn().mockResolvedValue({});
|
|
255
|
+
const onSharingChanged = vi.fn();
|
|
256
|
+
render(
|
|
257
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
258
|
+
<ShareAgentDialog
|
|
259
|
+
open
|
|
260
|
+
onOpenChange={() => {}}
|
|
261
|
+
agent={makeAgent({
|
|
262
|
+
enabled: false,
|
|
263
|
+
allowedOrigins: ["https://example.com"],
|
|
264
|
+
messages: { rateLimited: "Easy there." },
|
|
265
|
+
})}
|
|
266
|
+
buildShareUrl={buildShareUrl}
|
|
267
|
+
onSharingChanged={onSharingChanged}
|
|
268
|
+
/>
|
|
269
|
+
</Providers>,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
273
|
+
|
|
274
|
+
await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
|
|
275
|
+
const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
|
|
276
|
+
expect(input.resourceId).toBe("agt_1");
|
|
277
|
+
expect(input.sharing?.enabled).toBe(true);
|
|
278
|
+
// The RPC replaces spec.sharing wholesale — the toggle must carry the
|
|
279
|
+
// existing origins and messages or it would silently erase them.
|
|
280
|
+
expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
|
|
281
|
+
expect(input.sharing?.messages?.rateLimited).toBe("Easy there.");
|
|
282
|
+
await waitFor(() => expect(onSharingChanged).toHaveBeenCalled());
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("adopts the server's returned sharing state after a commit", async () => {
|
|
286
|
+
// Server echoes the update but with a normalized origin list.
|
|
287
|
+
const updateSharing = vi.fn().mockResolvedValue(
|
|
288
|
+
makeAgent({
|
|
289
|
+
enabled: true,
|
|
290
|
+
allowedOrigins: ["https://normalized.example.com"],
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
render(
|
|
294
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
295
|
+
<ShareAgentDialog
|
|
296
|
+
open
|
|
297
|
+
onOpenChange={() => {}}
|
|
298
|
+
agent={makeAgent({ enabled: false })}
|
|
299
|
+
buildShareUrl={buildShareUrl}
|
|
300
|
+
/>
|
|
301
|
+
</Providers>,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
305
|
+
await waitFor(() =>
|
|
306
|
+
expect(screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked")).toBe(
|
|
307
|
+
"true",
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
312
|
+
expect(screen.getByText("https://normalized.example.com")).toBeTruthy();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("Embed tab", () => {
|
|
316
|
+
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
|
|
317
|
+
render(
|
|
318
|
+
<Providers client={createMockStigmer()}>
|
|
319
|
+
<ShareAgentDialog
|
|
320
|
+
open
|
|
321
|
+
onOpenChange={() => {}}
|
|
322
|
+
agent={makeAgent({ enabled: true })}
|
|
323
|
+
buildShareUrl={buildShareUrl}
|
|
324
|
+
/>
|
|
325
|
+
</Providers>,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
329
|
+
// The loader must be served from the SAME origin as the share URL —
|
|
330
|
+
// embed.js derives the chat-page origin from its own script URL.
|
|
331
|
+
expect(
|
|
332
|
+
screen.getByText(
|
|
333
|
+
/<script src="https:\/\/app\.example\.com\/embed\.js" async><\/script>/,
|
|
334
|
+
),
|
|
335
|
+
).toBeTruthy();
|
|
336
|
+
expect(
|
|
337
|
+
screen.getByText(
|
|
338
|
+
/<stigmer-agent org="acme" agent="support-agent"><\/stigmer-agent>/,
|
|
339
|
+
),
|
|
340
|
+
).toBeTruthy();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
|
|
344
|
+
render(
|
|
345
|
+
<Providers client={createMockStigmer()}>
|
|
346
|
+
<ShareAgentDialog
|
|
347
|
+
open
|
|
348
|
+
onOpenChange={() => {}}
|
|
349
|
+
agent={makeAgent({ enabled: true })}
|
|
350
|
+
buildShareUrl={buildShareUrl}
|
|
351
|
+
/>
|
|
352
|
+
</Providers>,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
356
|
+
// Collapsed by default — the script snippet is the primary path.
|
|
357
|
+
expect(screen.queryByText(/<iframe/)).toBeNull();
|
|
358
|
+
|
|
359
|
+
fireEvent.click(
|
|
360
|
+
screen.getByRole("button", {
|
|
361
|
+
name: /No-JavaScript alternative/,
|
|
362
|
+
hidden: true,
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
expect(
|
|
366
|
+
screen.getByText(
|
|
367
|
+
/src="https:\/\/app\.example\.com\/chat\/acme\/support-agent"/,
|
|
368
|
+
),
|
|
369
|
+
).toBeTruthy();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("rejects an invalid origin without calling the RPC", async () => {
|
|
373
|
+
const updateSharing = vi.fn();
|
|
374
|
+
render(
|
|
375
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
376
|
+
<ShareAgentDialog
|
|
377
|
+
open
|
|
378
|
+
onOpenChange={() => {}}
|
|
379
|
+
agent={makeAgent({ enabled: true })}
|
|
380
|
+
buildShareUrl={buildShareUrl}
|
|
381
|
+
/>
|
|
382
|
+
</Providers>,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
386
|
+
const input = screen.getByLabelText("Add allowed origin");
|
|
387
|
+
fireEvent.change(input, {
|
|
388
|
+
target: { value: "https://example.com/path" },
|
|
389
|
+
});
|
|
390
|
+
fireEvent.click(screen.getByText("Add"));
|
|
391
|
+
|
|
392
|
+
expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
|
|
393
|
+
expect(updateSharing).not.toHaveBeenCalled();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("adds a valid origin by committing the appended list", async () => {
|
|
397
|
+
const updateSharing = vi.fn().mockResolvedValue({});
|
|
398
|
+
render(
|
|
399
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
400
|
+
<ShareAgentDialog
|
|
401
|
+
open
|
|
402
|
+
onOpenChange={() => {}}
|
|
403
|
+
agent={makeAgent({
|
|
404
|
+
enabled: true,
|
|
405
|
+
allowedOrigins: ["https://existing.example.com"],
|
|
406
|
+
})}
|
|
407
|
+
buildShareUrl={buildShareUrl}
|
|
408
|
+
/>
|
|
409
|
+
</Providers>,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
413
|
+
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
414
|
+
target: { value: "https://new.example.com" },
|
|
415
|
+
});
|
|
416
|
+
fireEvent.click(screen.getByText("Add"));
|
|
417
|
+
|
|
418
|
+
await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
|
|
419
|
+
const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
|
|
420
|
+
expect(input.sharing?.allowedOrigins).toEqual([
|
|
421
|
+
"https://existing.example.com",
|
|
422
|
+
"https://new.example.com",
|
|
423
|
+
]);
|
|
424
|
+
// Enabled state rides along untouched.
|
|
425
|
+
expect(input.sharing?.enabled).toBe(true);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("rejects a duplicate origin", async () => {
|
|
429
|
+
const updateSharing = vi.fn();
|
|
430
|
+
render(
|
|
431
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
432
|
+
<ShareAgentDialog
|
|
433
|
+
open
|
|
434
|
+
onOpenChange={() => {}}
|
|
435
|
+
agent={makeAgent({
|
|
436
|
+
enabled: true,
|
|
437
|
+
allowedOrigins: ["https://example.com"],
|
|
438
|
+
})}
|
|
439
|
+
buildShareUrl={buildShareUrl}
|
|
440
|
+
/>
|
|
441
|
+
</Providers>,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
445
|
+
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
446
|
+
target: { value: "https://example.com" },
|
|
447
|
+
});
|
|
448
|
+
fireEvent.click(screen.getByText("Add"));
|
|
449
|
+
|
|
450
|
+
expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
|
|
451
|
+
expect(updateSharing).not.toHaveBeenCalled();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("removes an origin by committing the filtered list", async () => {
|
|
455
|
+
const updateSharing = vi.fn().mockResolvedValue({});
|
|
456
|
+
render(
|
|
457
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
458
|
+
<ShareAgentDialog
|
|
459
|
+
open
|
|
460
|
+
onOpenChange={() => {}}
|
|
461
|
+
agent={makeAgent({
|
|
462
|
+
enabled: true,
|
|
463
|
+
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
464
|
+
})}
|
|
465
|
+
buildShareUrl={buildShareUrl}
|
|
466
|
+
/>
|
|
467
|
+
</Providers>,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
471
|
+
fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
|
|
472
|
+
|
|
473
|
+
await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
|
|
474
|
+
const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
|
|
475
|
+
expect(input.sharing?.allowedOrigins).toEqual(["https://b.example.com"]);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe("visitor messages", () => {
|
|
480
|
+
it("saves edited messages as part of the complete block", async () => {
|
|
481
|
+
const updateSharing = vi.fn().mockResolvedValue({});
|
|
482
|
+
render(
|
|
483
|
+
<Providers client={createMockStigmer({ updateSharing })}>
|
|
484
|
+
<ShareAgentDialog
|
|
485
|
+
open
|
|
486
|
+
onOpenChange={() => {}}
|
|
487
|
+
agent={makeAgent({
|
|
488
|
+
enabled: true,
|
|
489
|
+
allowedOrigins: ["https://example.com"],
|
|
490
|
+
})}
|
|
491
|
+
buildShareUrl={buildShareUrl}
|
|
492
|
+
/>
|
|
493
|
+
</Providers>,
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
497
|
+
fireEvent.change(screen.getByLabelText(/Rate limited/), {
|
|
498
|
+
target: { value: "Please slow down." },
|
|
499
|
+
});
|
|
500
|
+
fireEvent.click(screen.getByText("Save messages"));
|
|
501
|
+
|
|
502
|
+
await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
|
|
503
|
+
const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
|
|
504
|
+
expect(input.sharing?.messages?.rateLimited).toBe("Please slow down.");
|
|
505
|
+
expect(input.sharing?.enabled).toBe(true);
|
|
506
|
+
expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("caps each message at 300 characters", () => {
|
|
510
|
+
render(
|
|
511
|
+
<Providers client={createMockStigmer()}>
|
|
512
|
+
<ShareAgentDialog
|
|
513
|
+
open
|
|
514
|
+
onOpenChange={() => {}}
|
|
515
|
+
agent={makeAgent({ enabled: true })}
|
|
516
|
+
buildShareUrl={buildShareUrl}
|
|
517
|
+
/>
|
|
518
|
+
</Providers>,
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
522
|
+
const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
|
|
523
|
+
fireEvent.change(field, { target: { value: "x".repeat(400) } });
|
|
524
|
+
expect(field.value.length).toBe(300);
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe("who pays", () => {
|
|
529
|
+
it("shows the who-pays line with the balance on cloud", async () => {
|
|
530
|
+
const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
|
|
531
|
+
balance: { availableMicros: BigInt(12_500_000) },
|
|
532
|
+
});
|
|
533
|
+
render(
|
|
534
|
+
<Providers
|
|
535
|
+
client={createMockStigmer({ getOrCreateBillingAccount })}
|
|
536
|
+
mode="cloud"
|
|
537
|
+
>
|
|
538
|
+
<ShareAgentDialog
|
|
539
|
+
open
|
|
540
|
+
onOpenChange={() => {}}
|
|
541
|
+
agent={makeAgent({ enabled: true })}
|
|
542
|
+
buildShareUrl={buildShareUrl}
|
|
543
|
+
/>
|
|
544
|
+
</Providers>,
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
548
|
+
await waitFor(() =>
|
|
549
|
+
expect(screen.getByText(/\$12\.50 available/)).toBeTruthy(),
|
|
550
|
+
);
|
|
551
|
+
expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("degrades to the who-pays line alone in local mode (no billing fetch)", () => {
|
|
555
|
+
const getOrCreateBillingAccount = vi.fn();
|
|
556
|
+
render(
|
|
557
|
+
<Providers
|
|
558
|
+
client={createMockStigmer({ getOrCreateBillingAccount })}
|
|
559
|
+
mode="local"
|
|
560
|
+
>
|
|
561
|
+
<ShareAgentDialog
|
|
562
|
+
open
|
|
563
|
+
onOpenChange={() => {}}
|
|
564
|
+
agent={makeAgent({ enabled: true })}
|
|
565
|
+
buildShareUrl={buildShareUrl}
|
|
566
|
+
/>
|
|
567
|
+
</Providers>,
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
571
|
+
expect(screen.queryByText(/available/)).toBeNull();
|
|
572
|
+
expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it("degrades silently when the billing fetch fails", async () => {
|
|
576
|
+
const getOrCreateBillingAccount = vi
|
|
577
|
+
.fn()
|
|
578
|
+
.mockRejectedValue(new Error("billing unavailable"));
|
|
579
|
+
render(
|
|
580
|
+
<Providers
|
|
581
|
+
client={createMockStigmer({ getOrCreateBillingAccount })}
|
|
582
|
+
mode="cloud"
|
|
583
|
+
>
|
|
584
|
+
<ShareAgentDialog
|
|
585
|
+
open
|
|
586
|
+
onOpenChange={() => {}}
|
|
587
|
+
agent={makeAgent({ enabled: true })}
|
|
588
|
+
buildShareUrl={buildShareUrl}
|
|
589
|
+
/>
|
|
590
|
+
</Providers>,
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
|
|
594
|
+
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
595
|
+
expect(screen.queryByText(/available/)).toBeNull();
|
|
596
|
+
expect(screen.queryByText(/billing unavailable/)).toBeNull();
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
describe("Developer tab", () => {
|
|
601
|
+
it("shows the platform client snippet and docs link", () => {
|
|
602
|
+
render(
|
|
603
|
+
<Providers client={createMockStigmer()}>
|
|
604
|
+
<ShareAgentDialog
|
|
605
|
+
open
|
|
606
|
+
onOpenChange={() => {}}
|
|
607
|
+
agent={makeAgent({ enabled: true })}
|
|
608
|
+
buildShareUrl={buildShareUrl}
|
|
609
|
+
/>
|
|
610
|
+
</Providers>,
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
|
|
614
|
+
expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
|
|
615
|
+
const link = screen.getByText(/platform client guide/);
|
|
616
|
+
expect(link.getAttribute("href")).toContain("platform-client");
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it("renders in-flow without showModal when modal is false", () => {
|
|
621
|
+
const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
|
|
622
|
+
render(
|
|
623
|
+
<Providers client={createMockStigmer()}>
|
|
624
|
+
<ShareAgentDialog
|
|
625
|
+
open
|
|
626
|
+
onOpenChange={() => {}}
|
|
627
|
+
agent={makeAgent({ enabled: true })}
|
|
628
|
+
buildShareUrl={buildShareUrl}
|
|
629
|
+
modal={false}
|
|
630
|
+
/>
|
|
631
|
+
</Providers>,
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
expect(screen.getByText("Share")).toBeTruthy();
|
|
635
|
+
expect(showModal).not.toHaveBeenCalled();
|
|
636
|
+
showModal.mockRestore();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("requests close via Done and the close affordance", () => {
|
|
640
|
+
const onOpenChange = vi.fn();
|
|
641
|
+
render(
|
|
642
|
+
<Providers client={createMockStigmer()}>
|
|
643
|
+
<ShareAgentDialog
|
|
644
|
+
open
|
|
645
|
+
onOpenChange={onOpenChange}
|
|
646
|
+
agent={makeAgent({ enabled: true })}
|
|
647
|
+
buildShareUrl={buildShareUrl}
|
|
648
|
+
/>
|
|
649
|
+
</Providers>,
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
screen.getByText("Done").click();
|
|
653
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
654
|
+
|
|
655
|
+
onOpenChange.mockClear();
|
|
656
|
+
screen.getByLabelText("Close").click();
|
|
657
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
describe("Reset link (rotatable share token)", () => {
|
|
661
|
+
it("appends the status token to the shown link and embed snippet", () => {
|
|
662
|
+
render(
|
|
663
|
+
<Providers client={createMockStigmer()}>
|
|
664
|
+
<ShareAgentDialog
|
|
665
|
+
open
|
|
666
|
+
onOpenChange={() => {}}
|
|
667
|
+
agent={makeAgent({ enabled: true }, "tok123")}
|
|
668
|
+
buildShareUrl={buildShareUrl}
|
|
669
|
+
/>
|
|
670
|
+
</Providers>,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(
|
|
674
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
|
|
675
|
+
).toBeTruthy();
|
|
676
|
+
|
|
677
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
678
|
+
expect(screen.getByText(/token="tok123"/)).toBeTruthy();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it("rotates on Reset link, adopts the fresh token, and notifies the host", async () => {
|
|
682
|
+
const rotateShareLink = vi.fn().mockResolvedValue({
|
|
683
|
+
metadata: { id: "agt_1" },
|
|
684
|
+
status: { shareLinkToken: "fresh-token" },
|
|
685
|
+
});
|
|
686
|
+
const onSharingChanged = vi.fn();
|
|
687
|
+
render(
|
|
688
|
+
<Providers client={createMockStigmer({ rotateShareLink })}>
|
|
689
|
+
<ShareAgentDialog
|
|
690
|
+
open
|
|
691
|
+
onOpenChange={() => {}}
|
|
692
|
+
agent={makeAgent({ enabled: true })}
|
|
693
|
+
buildShareUrl={buildShareUrl}
|
|
694
|
+
onSharingChanged={onSharingChanged}
|
|
695
|
+
/>
|
|
696
|
+
</Providers>,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
// A plain link shows no token before the reset.
|
|
700
|
+
expect(
|
|
701
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
702
|
+
).toBeTruthy();
|
|
703
|
+
|
|
704
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset link", hidden: true }));
|
|
705
|
+
|
|
706
|
+
await waitFor(() =>
|
|
707
|
+
expect(
|
|
708
|
+
screen.getByText(
|
|
709
|
+
"https://app.example.com/chat/acme/support-agent?k=fresh-token",
|
|
710
|
+
),
|
|
711
|
+
).toBeTruthy(),
|
|
712
|
+
);
|
|
713
|
+
expect(rotateShareLink).toHaveBeenCalledTimes(1);
|
|
714
|
+
expect(onSharingChanged).toHaveBeenCalled();
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("hides the Reset control and the token for org-members-only shares", () => {
|
|
718
|
+
render(
|
|
719
|
+
<Providers client={createMockStigmer()}>
|
|
720
|
+
<ShareAgentDialog
|
|
721
|
+
open
|
|
722
|
+
onOpenChange={() => {}}
|
|
723
|
+
agent={makeAgent(
|
|
724
|
+
{
|
|
725
|
+
enabled: true,
|
|
726
|
+
audience: AgentSharingAudience.org,
|
|
727
|
+
},
|
|
728
|
+
"tok123",
|
|
729
|
+
)}
|
|
730
|
+
buildShareUrl={buildShareUrl}
|
|
731
|
+
/>
|
|
732
|
+
</Providers>,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// Org access is gated by membership, not the link token: the member
|
|
736
|
+
// link stays clean and the Reset lever is not offered.
|
|
737
|
+
expect(
|
|
738
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
739
|
+
).toBeTruthy();
|
|
740
|
+
expect(
|
|
741
|
+
screen.queryByRole("button", { name: "Reset link", hidden: true }),
|
|
742
|
+
).toBeNull();
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
});
|