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

364 lines
11 KiB
TypeScript

import { act, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
AgentGoalBanner,
describeGoal,
formatGoalElapsed,
goalStatusTitle,
isGoalBannerVisible,
type AgentGoalBannerLabels
} from "./AgentGoalBanner";
const labels: AgentGoalBannerLabels = {
titleActive: "Active goal",
titlePaused: "Paused goal",
titleBlocked: "Blocked goal",
titleUsageLimited: "Usage-limited goal",
titleBudgetLimited: "Budget-limited goal",
titleComplete: "Completed goal",
budgetUsage: (used, budget) => `${used}/${budget} tokens`,
clearHint: "Type /goal clear to clear",
editAction: "Edit goal",
pauseAction: "Pause goal",
resumeAction: "Resume goal",
clearAction: "Delete goal"
};
describe("isGoalBannerVisible", () => {
it("is hidden when the objective is empty", () => {
expect(isGoalBannerVisible("", "active")).toBe(false);
expect(isGoalBannerVisible(" ", "active")).toBe(false);
expect(isGoalBannerVisible(null, "active")).toBe(false);
});
it("is hidden for terminal statuses", () => {
expect(isGoalBannerVisible("Ship it", "complete")).toBe(false);
expect(isGoalBannerVisible("Ship it", "Completed")).toBe(false);
expect(isGoalBannerVisible("Ship it", "done")).toBe(false);
});
it("is visible for non-terminal statuses with an objective", () => {
expect(isGoalBannerVisible("Ship it", "active")).toBe(true);
expect(isGoalBannerVisible("Ship it", "paused")).toBe(true);
expect(isGoalBannerVisible("Ship it", "")).toBe(true);
});
});
describe("goalStatusTitle", () => {
it("maps statuses onto the leading banner title", () => {
expect(goalStatusTitle("active", labels)).toBe("Active goal");
expect(goalStatusTitle("paused", labels)).toBe("Paused goal");
expect(goalStatusTitle("usageLimited", labels)).toBe("Usage-limited goal");
expect(goalStatusTitle("", labels)).toBe("Active goal");
});
});
describe("formatGoalElapsed", () => {
it("renders compact elapsed time", () => {
expect(formatGoalElapsed(2)).toBe("2s");
expect(formatGoalElapsed(59)).toBe("59s");
expect(formatGoalElapsed(60)).toBe("1m");
expect(formatGoalElapsed(312)).toBe("5m 12s");
expect(formatGoalElapsed(3840)).toBe("1h 4m");
expect(formatGoalElapsed(-3)).toBe("0s");
});
});
describe("AgentGoalBanner", () => {
it("builds the shared goal description text", () => {
expect(
describeGoal({
objective: "Finish the migration",
elapsedSeconds: 125,
tokenBudget: 40000,
tokensUsed: 1200,
labels
})
).toBe("Finish the migration · 2m 5s · 1200/40000 tokens");
});
it("renders the status title, objective, and elapsed time", () => {
render(
<AgentGoalBanner
objective="Finish the migration"
status="active"
durationMs={2000}
labels={labels}
/>
);
const banner = screen.getByTestId("agent-gui-goal-banner");
expect(banner.getAttribute("role")).toBe("status");
expect(banner.textContent).toContain("Active goal");
expect(banner.textContent).toContain("Finish the migration");
expect(banner.textContent).toContain("2s");
// Reuses the muted (informational) chrome card, not warning/danger.
expect(banner.className).toContain("agent-gui-chrome__card--muted");
});
it("omits the elapsed time when the goal payload has none", () => {
render(
<AgentGoalBanner objective="No timer" status="paused" labels={labels} />
);
expect(
screen.getByTestId("agent-gui-goal-banner-description").textContent
).toBe("No timer");
});
it("includes budget usage when a token budget is set", () => {
render(
<AgentGoalBanner
objective="Keep tests green"
status="active"
tokenBudget={40000}
tokensUsed={1200}
labels={labels}
/>
);
expect(screen.getByTestId("agent-gui-goal-banner").textContent).toContain(
"1200/40000 tokens"
);
});
it("renders the clear hint when no action callbacks are provided", () => {
render(
<AgentGoalBanner objective="Read only" status="active" labels={labels} />
);
expect(
screen.getByTestId("agent-gui-goal-banner-clear-hint").textContent
).toBe("Type /goal clear to clear");
expect(screen.queryByRole("button")).toBeNull();
});
it("offers edit, pause, and delete actions for an active goal", () => {
const onEditObjective = vi.fn();
const onPauseGoal = vi.fn();
const onResumeGoal = vi.fn();
const onClearGoal = vi.fn();
render(
<AgentGoalBanner
objective="Ship it"
status="active"
labels={labels}
onEditObjective={onEditObjective}
onPauseGoal={onPauseGoal}
onResumeGoal={onResumeGoal}
onClearGoal={onClearGoal}
/>
);
expect(screen.queryByTestId("agent-gui-goal-banner-clear-hint")).toBeNull();
expect(screen.queryByTestId("agent-gui-goal-banner-resume")).toBeNull();
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-pause"));
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-clear"));
expect(onPauseGoal).toHaveBeenCalledTimes(1);
expect(onClearGoal).toHaveBeenCalledTimes(1);
expect(onResumeGoal).not.toHaveBeenCalled();
expect(onEditObjective).not.toHaveBeenCalled();
});
it("edits the objective inline and confirms with Enter", () => {
const onEditObjective = vi.fn();
render(
<AgentGoalBanner
objective="Ship it"
status="active"
labels={labels}
onEditObjective={onEditObjective}
/>
);
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-edit"));
const input = screen.getByTestId(
"agent-gui-goal-banner-edit-input"
) as HTMLInputElement;
expect(input.value).toBe("Ship it");
fireEvent.change(input, { target: { value: "Ship it faster" } });
fireEvent.keyDown(input, { key: "Enter" });
expect(onEditObjective).toHaveBeenCalledWith("Ship it faster");
expect(screen.queryByTestId("agent-gui-goal-banner-edit-input")).toBeNull();
});
it("cancels the inline edit with Escape without applying", () => {
const onEditObjective = vi.fn();
render(
<AgentGoalBanner
objective="Ship it"
status="active"
labels={labels}
onEditObjective={onEditObjective}
/>
);
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-edit"));
const input = screen.getByTestId("agent-gui-goal-banner-edit-input");
fireEvent.change(input, { target: { value: "Changed" } });
fireEvent.keyDown(input, { key: "Escape" });
expect(onEditObjective).not.toHaveBeenCalled();
expect(screen.queryByTestId("agent-gui-goal-banner-edit-input")).toBeNull();
});
it.each(["paused", "blocked", "usageLimited", "budgetLimited"])(
"offers resume and delete actions for a %s goal",
(status) => {
const onPauseGoal = vi.fn();
const onResumeGoal = vi.fn();
const onClearGoal = vi.fn();
render(
<AgentGoalBanner
objective="Ship it"
status={status}
labels={labels}
onPauseGoal={onPauseGoal}
onResumeGoal={onResumeGoal}
onClearGoal={onClearGoal}
/>
);
expect(screen.queryByTestId("agent-gui-goal-banner-pause")).toBeNull();
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-resume"));
fireEvent.click(screen.getByTestId("agent-gui-goal-banner-clear"));
expect(onResumeGoal).toHaveBeenCalledTimes(1);
expect(onClearGoal).toHaveBeenCalledTimes(1);
expect(onPauseGoal).not.toHaveBeenCalled();
}
);
describe("elapsed ticking", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(10_000);
});
afterEach(() => {
vi.useRealTimers();
});
it("ticks the elapsed time locally while the goal is active", () => {
render(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={8_000}
labels={labels}
/>
);
const description = () =>
screen.getByTestId("agent-gui-goal-banner-description").textContent;
expect(description()).toBe("Ship it · 2s");
act(() => {
vi.advanceTimersByTime(3000);
});
expect(description()).toBe("Ship it · 5s");
});
it("does not tick while the goal is paused", () => {
render(
<AgentGoalBanner
objective="Ship it"
status="paused"
durationMs={40000}
labels={labels}
/>
);
act(() => {
vi.advanceTimersByTime(5000);
});
expect(
screen.getByTestId("agent-gui-goal-banner-description").textContent
).toBe("Ship it · 40s");
});
it("starts an active goal from its canonical generation time", () => {
render(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={10_000}
labels={labels}
/>
);
const description = () =>
screen.getByTestId("agent-gui-goal-banner-description").textContent;
expect(description()).toBe("Ship it · 0s");
act(() => {
vi.advanceTimersByTime(3000);
});
expect(description()).toBe("Ship it · 3s");
});
it("does not show or start elapsed time for an optimistic goal", () => {
const { rerender } = render(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={6_000}
durationMs={4000}
optimistic={true}
labels={labels}
/>
);
const description = () =>
screen.getByTestId("agent-gui-goal-banner-description").textContent;
expect(description()).toBe("Ship it");
act(() => {
vi.advanceTimersByTime(3000);
});
expect(description()).toBe("Ship it");
rerender(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={6_000}
durationMs={7000}
optimistic={false}
labels={labels}
/>
);
expect(description()).toBe("Ship it · 7s");
act(() => {
vi.advanceTimersByTime(2000);
});
expect(description()).toBe("Ship it · 9s");
});
it("keeps elapsed time across unmount and remount", () => {
const first = render(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={8_000}
labels={labels}
/>
);
act(() => {
vi.advanceTimersByTime(3_000);
});
expect(
screen.getByTestId("agent-gui-goal-banner-description").textContent
).toBe("Ship it · 5s");
first.unmount();
render(
<AgentGoalBanner
objective="Ship it"
status="active"
startedAtUnixMs={8_000}
labels={labels}
/>
);
expect(
screen.getByTestId("agent-gui-goal-banner-description").textContent
).toBe("Ship it · 5s");
});
});
});