Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/view/AgentGUITextSelectionActions.spec.tsx
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

108 lines
3.3 KiB
TypeScript

// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import {
AgentGUITextSelectionActions,
readAgentGUITextSelection
} from "./AgentGUITextSelectionActions";
function selectMessageText(root: HTMLElement) {
const message = screen.getByTestId("message-text");
const range = document.createRange();
range.selectNodeContents(message);
Object.defineProperty(range, "getBoundingClientRect", {
value: () => ({
bottom: 120,
height: 20,
left: 100,
right: 260,
top: 100,
width: 160,
x: 100,
y: 100,
toJSON: () => ({})
})
});
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
return readAgentGUITextSelection(root);
}
function renderActions(onAskInSide?: (text: string) => void) {
const onAddToConversation = vi.fn();
const onDismiss = vi.fn();
const { container } = render(
<div data-testid="timeline-root">
<span data-testid="message-text">Selected answer text</span>
</div>
);
const snapshot = selectMessageText(
container.firstElementChild as HTMLElement
);
expect(snapshot).not.toBeNull();
render(
<AgentGUITextSelectionActions
labels={{
addToConversation: "Add to conversation",
askInSide: "Ask in Side chat"
}}
snapshot={snapshot!}
portalTarget={document.body}
onAddToConversation={onAddToConversation}
onAskInSide={onAskInSide}
onDismiss={onDismiss}
/>
);
return { onAddToConversation, onDismiss };
}
describe("AgentGUITextSelectionActions", () => {
it("routes selected transcript text to the main conversation or Side", () => {
const onAskInSide = vi.fn();
const { onAddToConversation, onDismiss } = renderActions(onAskInSide);
const toolbar = screen.getByRole("toolbar");
expect(toolbar).toHaveClass("t-dropdown");
expect(
screen.getByRole("button", { name: "Add to conversation" })
).toHaveAttribute("data-slot", "button");
expect(
screen.getByRole("button", { name: "Ask in Side chat" })
).toHaveClass("border-0", "border-l");
fireEvent.click(
screen.getByRole("button", { name: "Add to conversation" })
);
expect(onAddToConversation).toHaveBeenCalledWith("Selected answer text");
expect(onDismiss).toHaveBeenCalledOnce();
fireEvent.click(screen.getByRole("button", { name: "Ask in Side chat" }));
expect(onAskInSide).toHaveBeenCalledWith("Selected answer text");
});
it("hides the Side action when the exact live conversation lacks Side support", () => {
renderActions();
expect(
screen.getByRole("button", { name: "Add to conversation" })
).toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Ask in Side chat" })
).toBeNull();
});
it("ignores selections outside the transcript", () => {
const root = document.createElement("div");
const outside = document.createTextNode("outside");
document.body.append(root, outside);
const range = document.createRange();
range.selectNode(outside);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
expect(readAgentGUITextSelection(root)).toBeNull();
});
});