@runtypelabs/persona 4.30.0 → 4.32.0
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/README.md +22 -1
- package/dist/activity-ui.cjs +2 -2
- package/dist/activity-ui.d.cts +11 -3
- package/dist/activity-ui.d.ts +11 -3
- package/dist/activity-ui.js +1 -1
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/approval-ui.d.cts +2 -2
- package/dist/approval-ui.d.ts +2 -2
- package/dist/artifacts-ui.d.cts +3 -3
- package/dist/artifacts-ui.d.ts +3 -3
- package/dist/background-runtime.d.cts +1 -1
- package/dist/background-runtime.d.ts +1 -1
- package/dist/chunk-C2IbR39s.js +1 -0
- package/dist/chunk-CkE90FtA.js +2 -0
- package/dist/{chunk-n96BB0YH.js → chunk-CzZBmTLd.js} +1 -1
- package/dist/chunk-DABqu7C3.js +2 -0
- package/dist/chunk-DDAwu89-.js +1 -0
- package/dist/{chunk-CqEWCkJt.js → chunk-DZg9bFK6.js} +1 -1
- package/dist/chunk-DbD5qyBw.js +3 -0
- package/dist/{chunk-gayfbmEU.js → chunk-FEzOcZof.js} +1 -1
- package/dist/{chunk-UyZbbbz3.js → chunk-MdlQetfC.js} +2 -2
- package/dist/client-dispatch.js +1 -1
- package/dist/client-history.js +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/composer-actions-runtime.d.cts +2 -2
- package/dist/composer-actions-runtime.d.ts +2 -2
- package/dist/{composer-input-C76gfIoK.d.cts → composer-input-dwg_o9Z-.d.cts} +1 -1
- package/dist/{composer-input-C76gfIoK.d.ts → composer-input-dwg_o9Z-.d.ts} +1 -1
- package/dist/{composer-store-C7tvaz90.d.cts → composer-store-Dw7Zbqxr.d.cts} +1 -1
- package/dist/{composer-store-C7tvaz90.d.ts → composer-store-Dw7Zbqxr.d.ts} +1 -1
- package/dist/{context-mention-manager-IfT4JHma.d.cts → context-mention-manager-D2p0-JH-.d.cts} +2 -2
- package/dist/{context-mention-manager-IfT4JHma.d.ts → context-mention-manager-D2p0-JH-.d.ts} +2 -2
- package/dist/context-mentions-inline.d.cts +2 -2
- package/dist/context-mentions-inline.d.ts +2 -2
- package/dist/context-mentions.d.cts +3 -3
- package/dist/context-mentions.d.ts +3 -3
- package/dist/{defaults-Cc_p0PGM.d.cts → defaults-CjX0kKzb.d.cts} +1 -1
- package/dist/{defaults-Cc_p0PGM.d.ts → defaults-CjX0kKzb.d.ts} +1 -1
- package/dist/dictation-runtime.d.cts +2 -2
- package/dist/dictation-runtime.d.ts +2 -2
- package/dist/event-stream-view.d.cts +1 -1
- package/dist/event-stream-view.d.ts +1 -1
- package/dist/{fallback-speech-engine-CSxWpG0A.d.cts → fallback-speech-engine-DmF7MczI.d.cts} +1 -1
- package/dist/{fallback-speech-engine-CSxWpG0A.d.ts → fallback-speech-engine-DmF7MczI.d.ts} +1 -1
- package/dist/{feature-preferences-eeSOmDxg.d.cts → feature-preferences-CVFDt9Xf.d.cts} +1 -1
- package/dist/{feature-preferences-eeSOmDxg.d.ts → feature-preferences-CVFDt9Xf.d.ts} +1 -1
- package/dist/forms-ui.d.cts +2 -2
- package/dist/forms-ui.d.ts +2 -2
- package/dist/{history-confirm-dialog-DXkuAgh-.d.cts → history-confirm-dialog-DJEXsTyE.d.cts} +1 -1
- package/dist/{history-confirm-dialog-DXkuAgh-.d.ts → history-confirm-dialog-DJEXsTyE.d.ts} +1 -1
- package/dist/history-shell.d.cts +6 -6
- package/dist/history-shell.d.ts +6 -6
- package/dist/history-view.d.cts +1 -1
- package/dist/history-view.d.ts +1 -1
- package/dist/index.cjs +49 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +43 -41
- package/dist/index.js.map +1 -1
- package/dist/{loading-label-runtime-loader-C53YyPJ-.d.cts → loading-label-runtime-loader-CPxnoaoi.d.cts} +1 -1
- package/dist/{loading-label-runtime-loader-C53YyPJ-.d.ts → loading-label-runtime-loader-CPxnoaoi.d.ts} +1 -1
- package/dist/loading-label-runtime.d.cts +1 -1
- package/dist/loading-label-runtime.d.ts +1 -1
- package/dist/markdown-parsers.js +18 -15
- package/dist/{message-bubble-Cg_sTXUN.d.cts → message-bubble-CkZGlCLC.d.cts} +1 -1
- package/dist/{message-bubble-Cg_sTXUN.d.ts → message-bubble-CkZGlCLC.d.ts} +1 -1
- package/dist/preferences-runtime.d.cts +1 -1
- package/dist/preferences-runtime.d.ts +1 -1
- package/dist/{preview-BeMKALry.d.cts → preview-DdUvoLb4.d.cts} +2 -2
- package/dist/{preview-BeMKALry.d.ts → preview-DdUvoLb4.d.ts} +2 -2
- package/dist/{registry-BBe-Lesw.d.ts → registry-Dz4iHNST.d.cts} +3 -1
- package/dist/{registry-BBe-Lesw.d.cts → registry-Dz4iHNST.d.ts} +3 -1
- package/dist/{session-C0fSteA4.d.cts → session-Baxp9iRK.d.cts} +24 -9
- package/dist/{session-C0fSteA4.d.ts → session-Baxp9iRK.d.ts} +24 -9
- package/dist/session-reconnect.d.cts +2 -2
- package/dist/session-reconnect.d.ts +2 -2
- package/dist/smart-dom-reader.d.cts +1 -1
- package/dist/smart-dom-reader.d.ts +1 -1
- package/dist/theme-editor-preview.cjs +47 -45
- package/dist/theme-editor-preview.d.cts +1 -1
- package/dist/theme-editor-preview.d.ts +1 -1
- package/dist/theme-editor-preview.js +23 -24
- package/dist/theme-editor.d.cts +2 -2
- package/dist/theme-editor.d.ts +2 -2
- package/dist/theme-reference.d.cts +2 -2
- package/dist/theme-reference.d.ts +2 -2
- package/dist/{tool-loading-animation-BvdaZfwh.d.cts → tool-loading-animation-un3kA5Ur.d.cts} +1 -1
- package/dist/{tool-loading-animation-BvdaZfwh.d.ts → tool-loading-animation-un3kA5Ur.d.ts} +1 -1
- package/dist/{turn-activity-ui-BYBlInZQ.d.cts → turn-activity-ui-BGNhtMcH.d.cts} +2 -2
- package/dist/{turn-activity-ui-BYBlInZQ.d.ts → turn-activity-ui-BGNhtMcH.d.ts} +2 -2
- package/dist/turn-activity-ui.d.cts +1 -1
- package/dist/turn-activity-ui.d.ts +1 -1
- package/dist/{types-CiXi4ICM.d.cts → types-BOZHQIk0.d.cts} +13 -1
- package/dist/{types-CiXi4ICM.d.ts → types-BOZHQIk0.d.ts} +13 -1
- package/dist/voice-client-tools.d.cts +1 -1
- package/dist/voice-client-tools.d.ts +1 -1
- package/dist/voice-runtime.cjs +1 -1
- package/dist/voice-runtime.d.cts +2 -2
- package/dist/voice-runtime.d.ts +2 -2
- package/dist/voice-runtime.js +1 -1
- package/dist/voice-worklet-player.d.cts +2 -2
- package/dist/voice-worklet-player.d.ts +2 -2
- package/dist/{webmcp-runtime-entry-BTbOg3Au.d.cts → webmcp-runtime-entry-DuAG1_yF.d.cts} +1 -1
- package/dist/{webmcp-runtime-entry-BTbOg3Au.d.ts → webmcp-runtime-entry-DuAG1_yF.d.ts} +1 -1
- package/dist/webmcp-runtime.d.cts +1 -1
- package/dist/webmcp-runtime.d.ts +1 -1
- package/dist/widget.css +1 -1
- package/package.json +2 -2
- package/src/activity-ui.ts +23 -0
- package/src/components/disclosure-animation.ts +5 -0
- package/src/components/tool-bubble.ts +1 -0
- package/src/index-core.ts +2 -0
- package/src/markdown-parsers-eager.ts +2 -1
- package/src/markdown-parsers-entry.ts +1 -0
- package/src/markdown-parsers-loader.ts +2 -0
- package/src/postprocessors.ts +14 -0
- package/src/session.ts +3 -0
- package/src/styles/widget.css +6 -0
- package/src/types.ts +13 -0
- package/src/ui.background-conversations.test.ts +6 -0
- package/src/ui.footer-visibility.test.ts +54 -0
- package/src/ui.page-cache.test.ts +268 -0
- package/src/ui.postprocess.test.ts +80 -1
- package/src/ui.reveal-tool-call.test.ts +258 -0
- package/src/ui.ts +222 -32
- package/src/utils/actions.ts +3 -0
- package/src/utils/streaming-markdown.test.ts +103 -0
- package/src/utils/streaming-markdown.ts +186 -0
- package/dist/chunk-18tPmsiJ.js +0 -1
- package/dist/chunk-B3OrH9Ng.js +0 -2
- package/dist/chunk-Dp2vWE9V.js +0 -1
- package/dist/chunk-VloQ2dnh.js +0 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { createAgentExperience, type AgentWidgetController } from "./ui";
|
|
5
|
+
|
|
6
|
+
let controller: AgentWidgetController;
|
|
7
|
+
let mount: HTMLElement;
|
|
8
|
+
|
|
9
|
+
const injectTool = (id: string, sequence: number, status: "running" | "complete" = "complete", chunks = ["Tool details"]) =>
|
|
10
|
+
controller.injectTestMessage({
|
|
11
|
+
type: "message",
|
|
12
|
+
message: {
|
|
13
|
+
id: `msg-${id}`,
|
|
14
|
+
role: "assistant",
|
|
15
|
+
content: "",
|
|
16
|
+
createdAt: "2026-01-01T00:00:00Z",
|
|
17
|
+
sequence,
|
|
18
|
+
streaming: status === "running",
|
|
19
|
+
variant: "tool",
|
|
20
|
+
toolCall: { id, name: "Search", status, chunks },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const setup = (grouped: boolean, v5Defaults = true) => {
|
|
25
|
+
controller = createAgentExperience(mount, {
|
|
26
|
+
apiUrl: "/test",
|
|
27
|
+
persistState: false,
|
|
28
|
+
future: { v5Defaults },
|
|
29
|
+
launcher: { enabled: false },
|
|
30
|
+
features: {
|
|
31
|
+
toolCallDisplay: { grouped, groupedMode: "collapsible", autoExpand: false, autoCollapseDelay: false },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
injectTool("call-1", 1);
|
|
35
|
+
injectTool("call-2", 2);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const toolBubble = (toolCallId: string) =>
|
|
39
|
+
mount.querySelector<HTMLElement>(`.persona-tool-bubble[data-tool-call-id="${toolCallId}"]`);
|
|
40
|
+
const ownHeader = (bubble: HTMLElement) =>
|
|
41
|
+
Array.from(bubble.querySelectorAll<HTMLElement>('button[data-expand-header="true"]'))
|
|
42
|
+
.find((header) => header.closest(".persona-tool-bubble") === bubble)!;
|
|
43
|
+
|
|
44
|
+
const installScrollMetrics = () => {
|
|
45
|
+
const body = mount.querySelector<HTMLElement>(".persona-widget-body")!;
|
|
46
|
+
let height = 1000;
|
|
47
|
+
Object.defineProperties(body, {
|
|
48
|
+
clientHeight: { configurable: true, get: () => 400 },
|
|
49
|
+
scrollHeight: { configurable: true, get: () => height },
|
|
50
|
+
});
|
|
51
|
+
body.scrollTo = (options?: ScrollToOptions | number) => {
|
|
52
|
+
if (options && typeof options === "object") body.scrollTop = options.top ?? 0;
|
|
53
|
+
};
|
|
54
|
+
for (const [id, top] of [["call-1", 100], ["call-2", 900]] as const) {
|
|
55
|
+
Object.defineProperties(ownHeader(toolBubble(id)!), {
|
|
56
|
+
offsetTop: { configurable: true, get: () => top },
|
|
57
|
+
offsetParent: { configurable: true, get: () => body },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { body, grow: () => { height += 400; } };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.useFakeTimers();
|
|
65
|
+
window.localStorage.clear();
|
|
66
|
+
window.scrollTo = vi.fn();
|
|
67
|
+
mount = document.createElement("div");
|
|
68
|
+
document.body.appendChild(mount);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
controller?.destroy();
|
|
73
|
+
document.body.replaceChildren();
|
|
74
|
+
vi.unstubAllGlobals();
|
|
75
|
+
vi.useRealTimers();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("revealToolCall", () => {
|
|
79
|
+
it("stamps each tool bubble with its tool call id", () => {
|
|
80
|
+
setup(false);
|
|
81
|
+
expect(toolBubble("call-1")?.dataset.messageId).toBe("msg-call-1");
|
|
82
|
+
expect(toolBubble("call-2")?.dataset.messageId).toBe("msg-call-2");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("expands a standalone tool and marks it until the highlight expires", () => {
|
|
86
|
+
setup(false);
|
|
87
|
+
const bubble = toolBubble("call-2")!;
|
|
88
|
+
expect(ownHeader(bubble).getAttribute("aria-expanded")).toBe("false");
|
|
89
|
+
|
|
90
|
+
expect(controller.revealToolCall("call-2")).toBe(true);
|
|
91
|
+
expect(ownHeader(toolBubble("call-2")!).getAttribute("aria-expanded")).toBe("true");
|
|
92
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
93
|
+
expect(ownHeader(toolBubble("call-1")!).getAttribute("aria-expanded")).toBe("false");
|
|
94
|
+
|
|
95
|
+
vi.advanceTimersByTime(2000);
|
|
96
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("opens the collapsed group that holds the tool before expanding it", () => {
|
|
100
|
+
setup(true);
|
|
101
|
+
const group = mount.querySelector<HTMLElement>('[data-message-id="tool-group-msg-call-1"]')!;
|
|
102
|
+
expect(ownHeader(group).getAttribute("aria-expanded")).toBe("false");
|
|
103
|
+
|
|
104
|
+
expect(controller.revealToolCall("call-2")).toBe(true);
|
|
105
|
+
expect(ownHeader(group).getAttribute("aria-expanded")).toBe("true");
|
|
106
|
+
expect(ownHeader(toolBubble("call-2")!).getAttribute("aria-expanded")).toBe("true");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it.each([[false, false], [false, true], [true, false], [true, true]])(
|
|
110
|
+
"keeps the highlight through streaming renders (V5: %s, grouped: %s)",
|
|
111
|
+
(v5Defaults, grouped) => {
|
|
112
|
+
setup(grouped, v5Defaults);
|
|
113
|
+
injectTool("call-2", 2, "running", ["Searching"]);
|
|
114
|
+
controller.revealToolCall("call-2");
|
|
115
|
+
|
|
116
|
+
vi.advanceTimersByTime(1000);
|
|
117
|
+
injectTool("call-2", 2, "running", ["Searching", "Found a page"]);
|
|
118
|
+
expect(toolBubble("call-2")!.textContent).toContain("Found a page");
|
|
119
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
120
|
+
|
|
121
|
+
injectTool("call-2", 2, "complete", ["Search complete"]);
|
|
122
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
123
|
+
vi.advanceTimersByTime(999);
|
|
124
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
125
|
+
vi.advanceTimersByTime(1);
|
|
126
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
127
|
+
injectTool("call-2", 2, "complete", ["Updated result"]);
|
|
128
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
it("restarts the highlight duration when revealing the same tool again", () => {
|
|
133
|
+
setup(false);
|
|
134
|
+
controller.revealToolCall("call-2");
|
|
135
|
+
vi.advanceTimersByTime(1000);
|
|
136
|
+
controller.revealToolCall("call-2");
|
|
137
|
+
vi.advanceTimersByTime(1000);
|
|
138
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
139
|
+
vi.advanceTimersByTime(1000);
|
|
140
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("keeps the highlight when regrouping the transcript", () => {
|
|
144
|
+
setup(false);
|
|
145
|
+
controller.revealToolCall("call-2");
|
|
146
|
+
controller.update({ features: { toolCallDisplay: { grouped: true } } });
|
|
147
|
+
expect(toolBubble("call-2")!.parentElement?.closest("[data-persona-tool-group]")).not.toBeNull();
|
|
148
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
149
|
+
vi.advanceTimersByTime(2000);
|
|
150
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("does not carry a highlight into a cleared conversation with the same tool ids", () => {
|
|
154
|
+
setup(false);
|
|
155
|
+
controller.revealToolCall("call-2");
|
|
156
|
+
controller.clearChat();
|
|
157
|
+
injectTool("call-2", 2);
|
|
158
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it.each([false, true])("keeps following paused when centering reaches the bottom (reduced motion: %s)", (reduced) => {
|
|
162
|
+
vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: reduced, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
|
|
163
|
+
setup(false);
|
|
164
|
+
controller.update({ features: { scrollBehavior: { mode: "follow" } } });
|
|
165
|
+
const { body, grow } = installScrollMetrics();
|
|
166
|
+
controller.revealToolCall("call-2", { expand: false, highlightMs: 0 });
|
|
167
|
+
vi.advanceTimersByTime(500);
|
|
168
|
+
expect(body.scrollTop).toBe(600);
|
|
169
|
+
body.dispatchEvent(new Event("scroll"));
|
|
170
|
+
vi.advanceTimersByTime(200);
|
|
171
|
+
body.dispatchEvent(new Event("scroll"));
|
|
172
|
+
grow();
|
|
173
|
+
injectTool("call-2", 2, "running", ["More streamed output"]);
|
|
174
|
+
vi.advanceTimersByTime(500);
|
|
175
|
+
expect(body.scrollTop).toBe(600);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("cancels an earlier jump to latest and keeps following paused", () => {
|
|
179
|
+
setup(false);
|
|
180
|
+
controller.update({ features: {
|
|
181
|
+
scrollBehavior: { mode: "follow" },
|
|
182
|
+
scrollToBottom: { scrollBehavior: "smooth", scrollDurationMs: 800 },
|
|
183
|
+
} });
|
|
184
|
+
vi.advanceTimersByTime(1000);
|
|
185
|
+
const { body, grow } = installScrollMetrics();
|
|
186
|
+
body.scrollTop = 0;
|
|
187
|
+
body.dispatchEvent(new Event("scroll"));
|
|
188
|
+
mount.querySelector<HTMLElement>("[data-persona-scroll-to-bottom]")!.dispatchEvent(new MouseEvent("click"));
|
|
189
|
+
vi.advanceTimersByTime(80);
|
|
190
|
+
expect(body.scrollTop).toBeGreaterThan(0);
|
|
191
|
+
controller.revealToolCall("call-1", { expand: false });
|
|
192
|
+
vi.advanceTimersByTime(1000);
|
|
193
|
+
expect(body.scrollTop).toBe(0);
|
|
194
|
+
grow();
|
|
195
|
+
injectTool("call-2", 2, "running", ["More streamed output"]);
|
|
196
|
+
vi.advanceTimersByTime(500);
|
|
197
|
+
expect(body.scrollTop).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it.each([
|
|
201
|
+
["wheel", 80, false],
|
|
202
|
+
["wheel", 250, false],
|
|
203
|
+
["keydown", 80, false],
|
|
204
|
+
["touchmove", 80, false],
|
|
205
|
+
["pointerdown", 80, false],
|
|
206
|
+
["wheel", 80, true],
|
|
207
|
+
] as const)("yields to %s at %sms (reduced motion: %s)", (input, elapsed, reduced) => {
|
|
208
|
+
vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: reduced, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
|
|
209
|
+
setup(false);
|
|
210
|
+
controller.update({ features: { scrollBehavior: { mode: "follow" } } });
|
|
211
|
+
const { body } = installScrollMetrics();
|
|
212
|
+
controller.revealToolCall("call-2", { expand: false });
|
|
213
|
+
if (elapsed > 200) {
|
|
214
|
+
vi.advanceTimersByTime(180);
|
|
215
|
+
// A disclosure transition moves the target before the settle pass.
|
|
216
|
+
Object.defineProperty(ownHeader(toolBubble("call-2")!), "offsetTop", { configurable: true, get: () => 750 });
|
|
217
|
+
vi.advanceTimersByTime(elapsed - 180);
|
|
218
|
+
} else vi.advanceTimersByTime(elapsed);
|
|
219
|
+
body.dispatchEvent(input === "wheel" ? new WheelEvent("wheel", { deltaY: -100 })
|
|
220
|
+
: input === "keydown" ? new KeyboardEvent("keydown", { key: "ArrowUp" }) : new Event(input));
|
|
221
|
+
body.scrollTop = 123;
|
|
222
|
+
body.dispatchEvent(new Event("scroll"));
|
|
223
|
+
vi.advanceTimersByTime(20);
|
|
224
|
+
expect(body.scrollTop).toBe(123);
|
|
225
|
+
vi.advanceTimersByTime(480);
|
|
226
|
+
expect(body.scrollTop).toBe(123);
|
|
227
|
+
expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
|
|
228
|
+
controller.revealToolCall("call-2", { expand: false });
|
|
229
|
+
vi.advanceTimersByTime(500);
|
|
230
|
+
expect(body.scrollTop).toBe(elapsed > 200 ? 550 : 600);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("cancels the previous reveal's delayed centering", () => {
|
|
234
|
+
vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
|
|
235
|
+
setup(false);
|
|
236
|
+
const { body } = installScrollMetrics();
|
|
237
|
+
controller.revealToolCall("call-1", { expand: false, highlightMs: 0 });
|
|
238
|
+
vi.advanceTimersByTime(100);
|
|
239
|
+
controller.revealToolCall("call-2", { expand: false, highlightMs: 0 });
|
|
240
|
+
expect(body.scrollTop).toBe(600);
|
|
241
|
+
vi.advanceTimersByTime(150);
|
|
242
|
+
expect(body.scrollTop).toBe(600);
|
|
243
|
+
vi.advanceTimersByTime(50);
|
|
244
|
+
expect(body.scrollTop).toBe(600);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("leaves details closed and skips the mark when asked", () => {
|
|
248
|
+
setup(false);
|
|
249
|
+
expect(controller.revealToolCall("call-1", { expand: false, highlightMs: 0 })).toBe(true);
|
|
250
|
+
expect(ownHeader(toolBubble("call-1")!).getAttribute("aria-expanded")).toBe("false");
|
|
251
|
+
expect(toolBubble("call-1")!.hasAttribute("data-persona-revealed")).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("returns false for an unknown tool call id", () => {
|
|
255
|
+
setup(false);
|
|
256
|
+
expect(controller.revealToolCall("missing")).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
});
|