Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/agentGuiNodeViewConversation.spec.ts
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

229 lines
6.4 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { createElement } from "react";
import { describe, expect, it } from "vitest";
import type { AgentGUIConversationSummary } from "./model/agentGuiConversationModel";
import {
ConversationMeta,
filterConversationSectionsBySearchMatches
} from "./agentGuiNodeViewConversation";
describe("filterConversationSectionsBySearchMatches", () => {
it("removes sections without exact conversation id matches", () => {
const matching = conversation("matching", 2);
const unrelated = conversation("unrelated", 1);
const filtered = filterConversationSectionsBySearchMatches(
[
{
id: "project:app",
kind: "project",
label: "App",
project: null,
items: [matching]
},
{
id: "conversations",
kind: "conversations",
label: "Chats",
project: null,
items: [unrelated]
}
],
[matching]
);
expect(filtered.map((section) => section.id)).toEqual(["project:app"]);
expect(filtered[0]?.items.map((item) => item.id)).toEqual(["matching"]);
});
});
describe("ConversationMeta", () => {
it("uses a prominent spinner stroke for working conversations", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("working", nowMs, { status: "working" });
render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
const spinner = screen.getByTestId("agent-gui-conversation-spinner");
expect(
spinner.querySelectorAll('circle[stroke-width="2.25"]')
).toHaveLength(2);
});
it("keeps waiting background work in the running presentation", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("background-waiting", nowMs, {
status: "waiting",
needsUserAction: false
});
render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
expect(
screen.getByTestId("agent-gui-conversation-meta-background-waiting")
).toHaveAttribute("data-kind", "loading");
expect(screen.getByTestId("agent-gui-conversation-spinner")).toBeVisible();
});
it("keeps the worktree mark to the left of the loading indicator", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("working-worktree", nowMs, {
status: "working",
isolation: {
mode: "worktree",
worktreePath: "/state/worktrees/session",
branch: "tutti/session",
baseCommit: "abc123"
}
});
const { container } = render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
const meta = container.querySelector(".agent-gui-node__conversation-meta");
expect(meta?.firstElementChild).toHaveClass(
"agent-gui-node__conversation-worktree-glyph"
);
expect(meta?.lastElementChild).toHaveAttribute(
"data-testid",
"agent-gui-conversation-spinner"
);
});
it("shows the ask indicator whenever a conversation needs user action", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("user-action-waiting", nowMs, {
status: "working",
needsUserAction: true
});
render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
expect(
screen.getByTestId("agent-gui-conversation-meta-user-action-waiting")
).toHaveAttribute("data-kind", "waiting");
expect(
screen.queryByTestId("agent-gui-conversation-spinner")
).not.toBeInTheDocument();
});
it("shows a failed conversation instead of a stale user-action indicator", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("failed-with-stale-action", nowMs, {
status: "failed",
needsUserAction: true
});
render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
expect(
screen.getByTestId("agent-gui-conversation-meta-failed-with-stale-action")
).toHaveAttribute("data-kind", "failed");
});
it("displays the same sort time used by conversation ordering", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("updated-newer-sort-older", nowMs - 60 * 1000, {
sortTimeUnixMs: nowMs - 5 * 60 * 1000
});
render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
expect(
screen.getByTestId("agent-gui-conversation-meta-updated-newer-sort-older")
).toHaveTextContent("5 minutes");
});
it("shows the worktree mark alongside relative time outside hover state", () => {
const nowMs = new Date("2026-06-05T12:00:00Z").getTime();
const item = conversation("worktree", nowMs - 5 * 60 * 1000, {
isolation: {
mode: "worktree",
worktreePath: "/state/worktrees/session",
branch: "tutti/session",
baseCommit: "abc123"
}
});
const { container } = render(
createElement(ConversationMeta, {
item,
nowMs,
labels: relativeLabels
})
);
expect(screen.getByText("5 minutes")).toBeVisible();
expect(
container.querySelector(".agent-gui-node__conversation-worktree-glyph")
).toBeInTheDocument();
const meta = screen.getByTestId("agent-gui-conversation-meta-worktree");
expect(meta.firstElementChild).toHaveClass(
"agent-gui-node__conversation-worktree-glyph"
);
expect(meta.lastElementChild).toHaveClass(
"agent-gui-node__conversation-time"
);
});
});
const relativeLabels = {
relativeTimeJustNow: "Just now",
relativeTimeMinutes: (value: number) => `${value} minutes`,
relativeTimeHours: (value: number) => `${value} hours`,
relativeTimeDays: (value: number) => `${value} days`,
relativeTimeMonths: (value: number) => `${value} months`,
relativeTimeYears: (value: number) => `${value} years`
};
function conversation(
id: string,
updatedAtUnixMs: number,
overrides: Partial<AgentGUIConversationSummary> = {}
): AgentGUIConversationSummary {
return {
id,
provider: "codex",
title: id,
status: "ready",
cwd: "/workspace",
updatedAtUnixMs,
...overrides
};
}