惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
博客园_首页
U
Unit 42
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
MongoDB | Blog
MongoDB | Blog
美团技术团队
H
Help Net Security
G
Google Developers Blog
B
Blog RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
M
MIT News - Artificial intelligence
腾讯CDC
IT之家
IT之家
Vercel News
Vercel News
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
Last Week in AI
Last Week in AI
I
InfoQ
博客园 - 司徒正美
A
About on SuperTechFans

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
test(ui): split tool card render coverage · openclaw/open...
steipete · 2026-04-26 · via Recent Commits to openclaw:main

@@ -11,7 +11,6 @@ import {

1111

import { renderChatRunControls, type ChatRunControlsProps } from "./run-controls.ts";

1212

import { renderSideResult } from "./side-result-render.ts";

1313

import { renderCompactionIndicator, renderFallbackIndicator } from "./status-indicators.ts";

14-

import { renderToolCard } from "./tool-cards.ts";

15141615

vi.mock("../icons.ts", () => ({

1716

icons: {},

@@ -21,18 +20,6 @@ vi.mock("../markdown.ts", () => ({

2120

toSanitizedMarkdownHtml: (value: string) => value,

2221

}));

232224-

vi.mock("../tool-display.ts", () => ({

25-

formatToolDetail: () => undefined,

26-

resolveToolDisplay: ({ name }: { name: string }) => ({

27-

name,

28-

label: name

29-

.split(/[._-]/g)

30-

.map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part))

31-

.join(" "),

32-

icon: "zap",

33-

}),

34-

}));

35-3623

function createProps(overrides: Partial<ChatRunControlsProps> = {}): ChatRunControlsProps {

3724

return {

3825

canAbort: false,

@@ -380,170 +367,3 @@ describe("side result render", () => {

380367

expect(container.querySelector(".chat-side-result--error")).not.toBeNull();

381368

});

382369

});

383-384-

describe("tool-cards", () => {

385-

it("renders expanded cards with inline input and output sections", () => {

386-

const container = document.createElement("div");

387-

const toggle = vi.fn();

388-

render(

389-

renderToolCard(

390-

{

391-

id: "msg:4:call-4",

392-

name: "browser.open",

393-

args: { url: "https://example.com" },

394-

inputText: '{\n "url": "https://example.com"\n}',

395-

outputText: "Opened page",

396-

},

397-

{ expanded: true, onToggleExpanded: toggle },

398-

),

399-

container,

400-

);

401-402-

expect(container.textContent).toContain("Tool input");

403-

expect(container.textContent).toContain("Tool output");

404-

expect(container.textContent).toContain("https://example.com");

405-

expect(container.textContent).toContain("Opened page");

406-

});

407-408-

it("renders expanded tool calls without an inline output block when no output is present", () => {

409-

const container = document.createElement("div");

410-

render(

411-

renderToolCard(

412-

{

413-

id: "msg:4b:call-4b",

414-

name: "sessions_spawn",

415-

args: { mode: "session", thread: true },

416-

inputText: '{\n "mode": "session",\n "thread": true\n}',

417-

},

418-

{ expanded: true, onToggleExpanded: vi.fn() },

419-

),

420-

container,

421-

);

422-423-

expect(container.textContent).toContain("Tool input");

424-

expect(container.textContent).toContain('"thread": true');

425-

expect(container.textContent).not.toContain("Tool output");

426-

expect(container.textContent).not.toContain("No output");

427-

});

428-429-

it("labels collapsed tool calls as tool call", () => {

430-

const container = document.createElement("div");

431-

render(

432-

renderToolCard(

433-

{

434-

id: "msg:5:call-5",

435-

name: "sessions_spawn",

436-

args: { mode: "run" },

437-

inputText: '{\n "mode": "run"\n}',

438-

},

439-

{ expanded: false, onToggleExpanded: vi.fn() },

440-

),

441-

container,

442-

);

443-444-

expect(container.textContent).toContain("Tool call");

445-

expect(container.textContent).not.toContain("Tool input");

446-

const summaryButton = container.querySelector("button.chat-tool-msg-summary");

447-

expect(summaryButton).not.toBeNull();

448-

expect(summaryButton?.getAttribute("aria-expanded")).toBe("false");

449-

});

450-451-

it("keeps raw details for legacy canvas tool output without rendering tool-row previews", () => {

452-

const container = document.createElement("div");

453-

render(

454-

renderToolCard(

455-

{

456-

id: "msg:view:7",

457-

name: "canvas_render",

458-

outputText: JSON.stringify({

459-

kind: "canvas",

460-

view: {

461-

backend: "canvas",

462-

id: "cv_counter",

463-

url: "/__openclaw__/canvas/documents/cv_counter/index.html",

464-

title: "Counter demo",

465-

preferred_height: 480,

466-

},

467-

presentation: {

468-

target: "tool_card",

469-

},

470-

}),

471-

preview: {

472-

kind: "canvas",

473-

surface: "assistant_message",

474-

render: "url",

475-

viewId: "cv_counter",

476-

title: "Counter demo",

477-

url: "/__openclaw__/canvas/documents/cv_counter/index.html",

478-

preferredHeight: 480,

479-

},

480-

},

481-

{ expanded: true, onToggleExpanded: vi.fn() },

482-

),

483-

container,

484-

);

485-486-

const rawToggle = container.querySelector<HTMLButtonElement>(".chat-tool-card__raw-toggle");

487-

const rawBody = container.querySelector<HTMLElement>(".chat-tool-card__raw-body");

488-489-

expect(container.textContent).toContain("Counter demo");

490-

expect(container.querySelector(".chat-tool-card__preview-frame")).toBeNull();

491-

expect(rawToggle?.getAttribute("aria-expanded")).toBe("false");

492-

expect(rawBody?.hidden).toBe(true);

493-494-

rawToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

495-496-

expect(rawToggle?.getAttribute("aria-expanded")).toBe("true");

497-

expect(rawBody?.hidden).toBe(false);

498-

expect(rawBody?.textContent).toContain('"kind":"canvas"');

499-

});

500-501-

it("opens assistant-surface canvas payloads in the sidebar when explicitly requested", () => {

502-

const container = document.createElement("div");

503-

const onOpenSidebar = vi.fn();

504-

render(

505-

renderToolCard(

506-

{

507-

id: "msg:view:8",

508-

name: "canvas_render",

509-

outputText: JSON.stringify({

510-

kind: "canvas",

511-

view: {

512-

backend: "canvas",

513-

id: "cv_sidebar",

514-

url: "/__openclaw__/canvas/documents/cv_sidebar/index.html",

515-

title: "Player",

516-

preferred_height: 360,

517-

},

518-

presentation: {

519-

target: "assistant_message",

520-

},

521-

}),

522-

preview: {

523-

kind: "canvas",

524-

surface: "assistant_message",

525-

render: "url",

526-

viewId: "cv_sidebar",

527-

url: "/__openclaw__/canvas/documents/cv_sidebar/index.html",

528-

title: "Player",

529-

preferredHeight: 360,

530-

},

531-

},

532-

{ expanded: true, onToggleExpanded: vi.fn(), onOpenSidebar },

533-

),

534-

container,

535-

);

536-537-

const sidebarButton = container.querySelector<HTMLButtonElement>(".chat-tool-card__action-btn");

538-

sidebarButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

539-540-

expect(sidebarButton).not.toBeNull();

541-

expect(onOpenSidebar).toHaveBeenCalledWith(

542-

expect.objectContaining({

543-

kind: "canvas",

544-

docId: "cv_sidebar",

545-

entryUrl: "/__openclaw__/canvas/documents/cv_sidebar/index.html",

546-

}),

547-

);

548-

});

549-

});