Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
/* @vitest-environment jsdom */
import { render } from "lit";
import { describe, expect, it, vi } from "vitest";
import { buildToolCardSidebarContent, extractToolCards, renderToolCard } from "./tool-c ards.ts";
describe("tool-cards", () => {
it("pretty-prints structured args and pairs tool output onto the same card", () => {
const cards = extractToolCards(
{
role: "assistant",
toolCallId: "call-1",
content: [
{
type: "toolcall",
id: "call-1",
name: "browser.open",
arguments: { url: "https://example.com", retry: 0 },
},
{
type: "toolresult",
id: "call-1",
name: "browser.open",
text: "Opened page",
},
],
},
"msg:1",
);
expect(cards).toHaveLength(1);
expect(cards[0]).toMatchObject({
id: "msg:1:call-1",
name: "browser.open",
outputText: "Opened page",
});
expect(cards[0]?.inputText).toContain('"url": "https://example.com"');
expect(cards[0]?.inputText).toContain('"retry": 0');
});
it("preserves string args verbatim and keeps empty-output cards", () => {
const cards = extractToolCards(
{
role: "assistant",
toolCallId: "call-2",
content: [
{
type: "toolcall",
name: "deck_manage",
arguments: "with Example Deck",
},
],
},
"msg:2",
);
expect(cards).toHaveLength(1);
expect(cards[0]?.inputText).toBe("with Example Deck");
expect(cards[0]?.outputText).toBeUndefined();
});
it("preserves tool-call input payloads from tool_use blocks", () => {
const cards = extractToolCards(
{
role: "assistant",
content: [
{
type: "tool_use",
id: "call-2b",
name: "deck_manage",
input: { deck: "Example Deck", mode: "preview" },
},
],
},
"msg:2b",
);
expect(cards).toHaveLength(1);
expect(cards[0]?.inputText).toContain('"deck": "Example Deck"');
expect(cards[0]?.inputText).toContain('"mode": "preview"');
});
it("pairs interleaved nameless tool results in content order", () => {
const cards = extractToolCards(
{
role: "assistant",
content: [
{
type: "tool_use",
name: "browser.open",
input: { url: "https://example.com/a" },
},
{
type: "tool_result",
name: "browser.open",
text: "Opened A",
},
{
type: "tool_use",
name: "browser.open",
input: { url: "https://example.com/b" },
},
{
type: "tool_result",
name: "browser.open",
text: "Opened B",
},
],
},
"msg:ordered",
);
expect(cards).toHaveLength(2);
expect(cards[0]).toMatchObject({
inputText: '{\n "url": "https://example.com/a"\n}',
outputText: "Opened A",
});
expect(cards[1]).toMatchObject({
inputText: '{\n "url": "https://example.com/b"\n}',
outputText: "Opened B",
});
});
it("builds sidebar content with input and empty output status", () => {
const [card] = extractToolCards(
{
role: "assistant",
toolCallId: "call-3",
content: [
{
type: "toolcall",
name: "deck_manage",
arguments: "with Example Deck",
},
],
},
"msg:3",
);
const sidebar = buildToolCardSidebarContent(card);
expect(sidebar).toContain("## Deck Manage");
expect(sidebar).toContain("### Tool input");
expect(sidebar).toContain("with Example Deck");
expect(sidebar).toContain("### Tool output");
expect(sidebar).toContain("No output");
});
it("extracts canvas handle payloads into canvas previews", () => {
const [card] = extractToolCards(
{
role: "tool",
toolName: "canvas_render",
content: JSON.stringify({
kind: "canvas",
view: {
backend: "canvas",
id: "cv_inline",
url: "/__openclaw__/canvas/documents/cv_inline/index.html",
},
presentation: {
target: "assistant_message",
title: "Inline demo",
preferred_height: 420,
},
}),
},
"msg:view:1",
);
expect(card?.preview).toMatchObject({
kind: "canvas",
surface: "assistant_message",
render: "url",
viewId: "cv_inline",
url: "/__openclaw__/canvas/documents/cv_inline/index.html",
title: "Inline demo",
preferredHeight: 420,
});
});
it("drops tool_card-targeted canvas payloads", () => {
const [card] = extractToolCards(
{
role: "tool",
toolName: "canvas_render",
content: JSON.stringify({
kind: "canvas",
view: {
backend: "canvas",
id: "cv_tool_card",
url: "/__openclaw__/canvas/documents/cv_tool_card/index.html",
},
presentation: {
target: "tool_card",
title: "Tool card demo",
},
}),
},
"msg:view:2",
);
expect(card?.preview).toBeUndefined();
});
it("does not extract inline-html canvas payloads into canvas previews", () => {
const [card] = extractToolCards(
{
role: "tool",
toolName: "canvas_render",
content: JSON.stringify({
kind: "canvas",
source: {
type: "html",
content: "<div>hello</div>",
},
presentation: {
target: "assistant_message",
title: "Status",
preferred_height: 300,
},
}),
},
"msg:view:3",
);
expect(card?.preview).toBeUndefined();
});
it("does not create a view preview for malformed json output", () => {
const [card] = extractToolCards(
{
role: "tool",
toolName: "canvas_render",
content: '{"kind":"present_view","view":{"id":"broken"}',
},
"msg:view:4",
);
expect(card?.preview).toBeUndefined();
});
it("does not create a view preview for generic tool text output", () => {
const [card] = extractToolCards(
{
role: "tool",
toolName: "browser.open",
content: "present_view: cv_widget",
},
"msg:view:5",
);
expect(card?.preview).toBeUndefined();
});
it("renders expanded cards with inline input and output sections", () => {
const container = document.createElement("div");
const toggle = vi.fn();
render(
renderToolCard(
{
id: "msg:4:call-4",
name: "browser.open",
args: { url: "https://example.com" },
inputText: '{\n "url": "https://example.com"\n}',
outputText: "Opened page",
},
{ expanded: true, onToggleExpanded: toggle },
),
container,
);
expect(container.textContent).toContain("Tool input");
expect(container.textContent).toContain("Tool output");
expect(container.textContent).toContain("https://example.com");
expect(container.textContent).toContain("Opened page");
});
it("renders expanded tool calls without an inline output block when no output is present", () => {
const container = document.createElement("div");
render(
renderToolCard(
{
id: "msg:4b:call-4b",
name: "sessions_spawn",
args: { mode: "session", thread: true },
inputText: '{\n "mode": "session",\n "thread": true\n}',
},
{ expanded: true, onToggleExpanded: vi.fn() },
),
container,
);
expect(container.textContent).toContain("Tool input");
expect(container.textContent).toContain('"thread": true');
expect(container.textContent).not.toContain("Tool output");
expect(container.textContent).not.toContain("No output");
});
it("labels collapsed tool calls as tool call", () => {
const container = document.createElement("div");
render(
renderToolCard(
{
id: "msg:5:call-5",
name: "sessions_spawn",
args: { mode: "run" },
inputText: '{\n "mode": "run"\n}',
},
{ expanded: false, onToggleExpanded: vi.fn() },
),
container,
);
expect(container.textContent).toContain("Tool call");
expect(container.textContent).not.toContain("Tool input");
const summaryButton = container.querySelector("button.chat-tool-msg-summary");
expect(summaryButton).not.toBeNull();
expect(summaryButton?.getAttribute("aria-expanded")).toBe("false");
});
it("keeps raw details for legacy canvas tool output without rendering tool-row previews", () => {
const container = document.createElement("div");
render(
renderToolCard(
{
id: "msg:view:7",
name: "canvas_render",
outputText: JSON.stringify({
kind: "canvas",
view: {
backend: "canvas",
id: "cv_counter",
url: "/__openclaw__/canvas/documents/cv_counter/index.html",
title: "Counter demo",
preferred_height: 480,
},
presentation: {
target: "tool_card",
},
}),
preview: {
kind: "canvas",
surface: "assistant_message",
render: "url",
viewId: "cv_counter",
title: "Counter demo",
url: "/__openclaw__/canvas/documents/cv_counter/index.html",
preferredHeight: 480,
},
},
{ expanded: true, onToggleExpanded: vi.fn() },
),
container,
);
const rawToggle = container.querySelector<HTMLButtonElement>(".chat-tool-card__raw-toggle");
const rawBody = container.querySelector<HTMLElement>(".chat-tool-card__raw-body");
expect(container.textContent).toContain("Counter demo");
expect(container.querySelector(".chat-tool-card__preview-frame")).toBeNull();
expect(rawToggle?.getAttribute("aria-expanded")).toBe("false");
expect(rawBody?.hidden).toBe(true);
rawToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(rawToggle?.getAttribute("aria-expanded")).toBe("true");
expect(rawBody?.hidden).toBe(false);
expect(rawBody?.textContent).toContain('"kind":"canvas"');
});
it("opens assistant-surface canvas payloads in the sidebar when explicitly requested", () => {
const container = document.createElement("div");
const onOpenSidebar = vi.fn();
render(
renderToolCard(
{
id: "msg:view:8",
name: "canvas_render",
outputText: JSON.stringify({
kind: "canvas",
view: {
backend: "canvas",
id: "cv_sidebar",
url: "/__openclaw__/canvas/documents/cv_sidebar/index.html",
title: "Player",
preferred_height: 360,
},
presentation: {
target: "assistant_message",
},
}),
preview: {
kind: "canvas",
surface: "assistant_message",
render: "url",
viewId: "cv_sidebar",
url: "/__openclaw__/canvas/documents/cv_sidebar/index.html",
title: "Player",
preferredHeight: 360,
},
},
{ expanded: true, onToggleExpanded: vi.fn(), onOpenSidebar },
),
container,
);
const sidebarButton = container.querySelector<HTMLButtonElement>(".chat-tool-card__action-btn");
sidebarButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(sidebarButton).not.toBeNull();
expect(onOpenSidebar).toHaveBeenCalledWith(
expect.objectContaining({
kind: "canvas",
docId: "cv_sidebar",
entryUrl: "/__openclaw__/canvas/documents/cv_sidebar/index.html",
}),
);
});
});
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|