This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
@@ -0,0 +1,159 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import {
TOOL_RUNWAY_DETAIL_LIMIT,
TOOL_RUNWAY_UPDATE_CADENCE_MS,
ToolStatusRunway,
} from "../src/components/ToolStatusRunway";
import {
cancelActiveToolStatus,
createToolStatusState,
reduceToolStatusEvent,
} from "../src/lib/tool-status-model";
import type { ToolStatusContractEvent } from "../src/lib/api";
describe("ToolStatusRunway", () => {
it("prioritizes the current tool action in one compact line", () => {
const state = runningState();
const html = render(state);
assert.match(html, /tool-runway-current/);
assert.match(html, /正在/);
assert.match(html, /读取/);
assert.match(html, /wiki\/mamba\.md/);
assert.equal(html.includes("tool-runway-targets"), false);
});
it("renders a single running tool with a compact target and live state", () => {
const state = runningState("~/projects/private/wiki/sources/very-long-file-name-for-runway-layout.md");
const html = renderToStaticMarkup(React.createElement(ToolStatusRunway, { state }));
assert.match(html, /tool-runway-running/);
assert.match(html, /tool-runway-pulse/);
assert.match(html, /读取/);
assert.match(html, /\.\.\./);
assert.match(html, /very-long-file-name-for-runway-layout\.md/);
assert.equal(html.includes("另有"), false);
});
it("renders done, failed, and cancelled states with distinct classes", () => {
const done = complete("done");
const failed = complete("failed");
const cancelled = cancelActiveToolStatus(
reduceToolStatusEvent(
createToolStatusState("runway-run", "runway-message"),
event("tool_status_start", {
seq: 1,
toolCallId: "bash-1",
toolName: "bash",
action: "运行命令",
target: "npm test",
status: "running",
args: { command: "npm test" },
runningToolCount: 1,
otherRunningCount: 0,
}),
{ nowMs: 1_000 },
),
"用户已停止",
);
assert.match(render(done), /tool-runway-done/);
assert.match(render(failed), /tool-runway-failed/);
assert.match(render(cancelled), /tool-runway-cancelled/);
assert.match(render(cancelled), /用户已停止/);
});
it("exposes concrete visual limits and update cadence", () => {
assert.equal(TOOL_RUNWAY_DETAIL_LIMIT, 50);
assert.ok(TOOL_RUNWAY_UPDATE_CADENCE_MS >= 80);
assert.ok(TOOL_RUNWAY_UPDATE_CADENCE_MS <= 150);
});
it("keeps the Paper runway treatment in CSS without losing compact information", () => {
const css = readFileSync(resolve(import.meta.dirname, "../src/index.css"), "utf8");
assert.match(css, /\.tool-runway::after/);
assert.match(css, /\.tool-runway[\s\S]*var\(--paper-grain\)/);
assert.match(css, /\.tool-runway-status[\s\S]*border-radius:\s*999px/);
assert.match(css, /\.tool-runway-current[\s\S]*white-space:\s*nowrap/);
});
});
function render(state: ReturnType<typeof createToolStatusState>): string {
return renderToStaticMarkup(React.createElement(ToolStatusRunway, { state }));
}
function runningState(target = "wiki/mamba.md") {
return reduceToolStatusEvent(
createToolStatusState("runway-run", "runway-message"),
event("tool_status_start", {
seq: 1,
toolCallId: "read-1",
toolName: "read",
action: "读取",
target,
status: "running",
args: { path: target },
runningToolCount: 1,
otherRunningCount: 0,
}),
{ nowMs: 1_000 },
);
}
function complete(status: "done" | "failed") {
let state = createToolStatusState("runway-run", "runway-message");
state = reduceToolStatusEvent(
state,
event("tool_status_start", {
seq: 1,
toolCallId: "read-1",
toolName: "read",
action: "读取",
target: "wiki/source.md",
status: "running",
args: { path: "wiki/source.md" },
runningToolCount: 1,
otherRunningCount: 0,
}),
{ nowMs: 1_000 },
);
return reduceToolStatusEvent(
state,
event("tool_status_end", {
seq: 2,
toolCallId: "read-1",
toolName: "read",
action: "读取",
target: "wiki/source.md",
status,
result: null,
summary: status === "done" ? "ok" : "failed",
error: status === "failed" ? "failed" : null,
durationMs: 20,
runningToolCount: 0,
otherRunningCount: 0,
}),
{ nowMs: 1_020 },
);
}
function event<T extends ToolStatusContractEvent["type"]>(
type: T,
payload: Omit<Extract<ToolStatusContractEvent, { type: T }>, "schemaVersion" | "type" | "runId" | "messageId">,
): Extract<ToolStatusContractEvent, { type: T }> {
return {
schemaVersion: 1,
type,
runId: "runway-run",
messageId: "runway-message",
...payload,
} as Extract<ToolStatusContractEvent, { type: T }>;
}