Files
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

192 lines
5.8 KiB
TypeScript

import "@testing-library/jest-dom/vitest";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { AgentGUIAgentTarget } from "../../../types";
import { AgentHandoffMenu } from "./AgentHandoffMenu";
beforeAll(() => {
Object.defineProperties(HTMLElement.prototype, {
hasPointerCapture: { configurable: true, value: () => false },
releasePointerCapture: { configurable: true, value: () => undefined },
setPointerCapture: { configurable: true, value: () => undefined }
});
});
describe("AgentHandoffMenu", () => {
it("shows its design-system tooltip without relying on a native title", async () => {
render(
<AgentHandoffMenu labels={labels} targets={targets} onSelect={vi.fn()} />
);
const trigger = screen.getByRole("combobox", { name: "Handoff" });
expect(trigger).not.toHaveAttribute("title");
fireEvent.pointerMove(trigger.parentElement!, {
pointerType: "mouse"
});
expect(await screen.findByRole("tooltip")).toHaveTextContent(
"Hand off to another agent"
);
});
it("does not create an empty tooltip when its tooltip label is blank", async () => {
render(
<AgentHandoffMenu
labels={{ ...labels, tooltip: " " }}
targets={targets}
onSelect={vi.fn()}
/>
);
const trigger = screen.getByRole("combobox", { name: "Handoff" });
fireEvent.pointerMove(trigger.parentElement!, { pointerType: "mouse" });
await new Promise((resolve) => setTimeout(resolve, 160));
expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
});
it("renders self and shared ownership and selects the exact target", async () => {
const onSelect = vi.fn();
render(
<AgentHandoffMenu
labels={labels}
showOwnershipLabels
targets={targets}
triggerLabel="Handoff"
onSelect={onSelect}
/>
);
fireEvent.pointerDown(screen.getByRole("combobox", { name: "Handoff" }), {
button: 0,
ctrlKey: false,
pointerType: "mouse"
});
const options = await screen.findAllByRole("option");
expect(options).toHaveLength(3);
const selfTarget = options[0]!;
const sharedTarget = options[1]!;
const offlineTarget = options[2]!;
expect(selfTarget).toHaveTextContent("Research Codex");
expect(sharedTarget).toHaveTextContent("Claude Code");
expect(selfTarget).toBeEnabled();
expect(sharedTarget).toBeEnabled();
expect(within(selfTarget).getByText("My Agent")).toBeVisible();
expect(within(selfTarget).getByText("From My Mac mini")).toBeVisible();
expect(within(sharedTarget).getByText("Lin · Shared Agent")).toBeVisible();
expect(
within(sharedTarget).getByText("From Lin's MacBook Pro")
).toBeVisible();
expect(offlineTarget).toHaveTextContent("Offline Agent");
expect(offlineTarget).toHaveAttribute("aria-disabled", "true");
fireEvent.pointerDown(sharedTarget, { button: 0, ctrlKey: false });
fireEvent.click(sharedTarget);
expect(onSelect).toHaveBeenCalledOnce();
expect(onSelect).toHaveBeenCalledWith(targets[1]);
});
it("hides ownership labels by default", async () => {
render(
<AgentHandoffMenu
labels={labels}
targets={targets.filter((target) => target.ownership === "self")}
onSelect={vi.fn()}
/>
);
fireEvent.pointerDown(screen.getByRole("combobox", { name: "Handoff" }), {
button: 0,
ctrlKey: false,
pointerType: "mouse"
});
const options = await screen.findAllByRole("option");
expect(options).toHaveLength(2);
expect(screen.queryByText("My Agent")).not.toBeInTheDocument();
expect(within(options[0]!).getByText("From My Mac mini")).toBeVisible();
});
it("supports an icon-only trigger and owns hover animation state", () => {
const onParentClick = vi.fn();
const { container } = render(
<div onClick={onParentClick}>
<AgentHandoffMenu
iconOnly
isolateTriggerEvents
labels={labels}
targets={targets.slice(0, 2)}
onSelect={vi.fn()}
/>
</div>
);
const trigger = screen.getByRole("combobox", { name: "Handoff" });
expect(trigger).not.toHaveTextContent("Handoff");
expect(
container.querySelector(".agent-gui-node__composer-handoff-icon")
).not.toHaveAttribute("data-playing");
fireEvent.mouseEnter(trigger);
expect(
container.querySelector(".agent-gui-node__composer-handoff-icon")
).toHaveAttribute("data-playing", "true");
fireEvent.mouseLeave(trigger);
expect(
container.querySelector(".agent-gui-node__composer-handoff-icon")
).not.toHaveAttribute("data-playing");
fireEvent.click(trigger);
expect(onParentClick).not.toHaveBeenCalled();
});
});
const labels = {
action: "Handoff",
deviceSource: (deviceLabel: string) => `From ${deviceLabel}`,
menu: "Choose an agent for handoff",
self: "My Agent",
shared: "Shared Agent",
tooltip: "Hand off to another agent"
};
const targets: readonly AgentGUIAgentTarget[] = [
target({
targetId: "target-self",
label: "Research Codex",
ownerDeviceLabel: "My Mac mini",
ownership: "self"
}),
target({
targetId: "target-shared",
label: "Claude Code",
ownerLabel: "Lin",
ownerDeviceLabel: "Lin's MacBook Pro",
ownership: "shared",
badge: { iconUrl: "owner.png", label: "Lin" }
}),
target({
targetId: "target-offline",
label: "Offline Agent",
ownership: "self",
disabled: true
})
];
function target(
input: Pick<AgentGUIAgentTarget, "label" | "targetId"> &
Partial<AgentGUIAgentTarget>
): AgentGUIAgentTarget {
return {
agentTargetId: input.targetId,
provider: "codex",
ref: { kind: "agent-directory", provider: "codex" },
iconUrl: "agent.png",
...input
};
}