Skip to content

Commit 05fed53

Browse files
committed
test(messageView): 增加渲染和解析工具函数的单元测试覆盖
- 新增 renderMessageToStdout 函数的多场景测试,包括用户、助手、工具和系统消息的渲染行为 - 添加 getUpdatePlanPreviewLines 对 UpdatePlan 工具消息的计划内容提取测试 - 增加 parseToolPayload 函数对空内容、无效 JSON 和有效负载的解析测试 - 引入辅助函数 makeSessionMessage 以简化测试消息实例构造 - 确保各种边界条件和meta字段的渲染正确性验证
1 parent a42d5de commit 05fed53

1 file changed

Lines changed: 177 additions & 1 deletion

File tree

src/tests/messageView.test.ts

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { test } from "node:test";
22
import assert from "node:assert/strict";
33
import { parseDiffPreview } from "../ui";
4-
import { buildThinkingSummary } from "../ui/compoments/MessageView/utils";
4+
import {
5+
buildThinkingSummary,
6+
renderMessageToStdout,
7+
getUpdatePlanPreviewLines,
8+
parseToolPayload,
9+
} from "../ui/compoments/MessageView/utils";
510
import { RawMode } from "../ui/contexts";
11+
import type { SessionMessage } from "../session";
12+
import type { ToolSummary } from "../ui/compoments/MessageView/types";
613

714
test("parseDiffPreview removes headers and classifies lines", () => {
815
const lines = parseDiffPreview(
@@ -52,3 +59,172 @@ test("MessageView shows full reasoning content in Normal/Raw mode", () => {
5259
"hidden chain of thought"
5360
);
5461
});
62+
63+
// --- renderMessageToStdout tests ---
64+
65+
function makeSessionMessage(overrides: Partial<SessionMessage> & Pick<SessionMessage, "role">): SessionMessage {
66+
const now = new Date().toISOString();
67+
return {
68+
id: `test-${Math.random().toString(36).slice(2)}`,
69+
sessionId: "test-session",
70+
visible: true,
71+
compacted: false,
72+
createTime: now,
73+
updateTime: now,
74+
contentParams: null,
75+
messageParams: null,
76+
...overrides,
77+
};
78+
}
79+
80+
test("renderMessageToStdout returns empty for invisible messages", () => {
81+
const msg = makeSessionMessage({ role: "user", content: "hello", visible: false });
82+
assert.equal(renderMessageToStdout(msg, RawMode.Raw), "");
83+
});
84+
85+
test("renderMessageToStdout renders user messages with > prefix", () => {
86+
const msg = makeSessionMessage({ role: "user", content: "fix the bug" });
87+
const output = renderMessageToStdout(msg, RawMode.Raw);
88+
assert.ok(output.includes("> fix the bug"));
89+
});
90+
91+
test("renderMessageToStdout shows (no content) for empty user messages", () => {
92+
const msg = makeSessionMessage({ role: "user", content: "" });
93+
const output = renderMessageToStdout(msg, RawMode.Raw);
94+
assert.ok(output.includes("(no content)"));
95+
});
96+
97+
test("renderMessageToStdout renders assistant non-thinking messages with ✦", () => {
98+
const msg = makeSessionMessage({ role: "assistant", content: "Here is the fix" });
99+
const output = renderMessageToStdout(msg, RawMode.Raw);
100+
assert.ok(output.includes("✦"));
101+
assert.ok(output.includes("Here is the fix"));
102+
});
103+
104+
test("renderMessageToStdout renders assistant thinking messages with ✧ Thinking", () => {
105+
const msg = makeSessionMessage({
106+
role: "assistant",
107+
content: "Plan:\nAnalyze the code",
108+
meta: { asThinking: true },
109+
});
110+
const output = renderMessageToStdout(msg, RawMode.Lite);
111+
assert.ok(output.includes("✧"));
112+
assert.ok(output.includes("Thinking"));
113+
assert.ok(output.includes("Plan: Analyze the code"));
114+
});
115+
116+
test("renderMessageToStdout renders tool messages with ✧ and tool name", () => {
117+
const payload = JSON.stringify({ name: "read", ok: true });
118+
const msg = makeSessionMessage({ role: "tool", content: payload });
119+
const output = renderMessageToStdout(msg, RawMode.Raw);
120+
assert.ok(output.includes("✧"));
121+
assert.ok(output.includes("Read"));
122+
});
123+
124+
test("renderMessageToStdout renders UpdatePlan tool messages with Plan preview", () => {
125+
const payload = JSON.stringify({
126+
name: "UpdatePlan",
127+
ok: true,
128+
metadata: { plan: "Step 1: Analyze\nStep 2: Implement\nStep 3: Test" },
129+
});
130+
const msg = makeSessionMessage({ role: "tool", content: payload });
131+
const output = renderMessageToStdout(msg, RawMode.Raw);
132+
assert.ok(output.includes("UpdatePlan"));
133+
assert.ok(output.includes("└ Plan"));
134+
assert.ok(output.includes("Step 1: Analyze"));
135+
assert.ok(output.includes("Step 2: Implement"));
136+
});
137+
138+
test("renderMessageToStdout renders system model change messages", () => {
139+
const msg = makeSessionMessage({
140+
role: "system",
141+
content: "Switched to deepseek-v4-pro",
142+
meta: { isModelChange: true },
143+
});
144+
const output = renderMessageToStdout(msg, RawMode.Raw);
145+
assert.ok(output.includes("> Switched to deepseek-v4-pro"));
146+
});
147+
148+
test("renderMessageToStdout renders system skill load messages", () => {
149+
const msg = makeSessionMessage({
150+
role: "system",
151+
content: "",
152+
meta: { skill: { name: "code-review" } },
153+
});
154+
const output = renderMessageToStdout(msg, RawMode.Raw);
155+
assert.ok(output.includes("⚡ Loaded skill: code-review"));
156+
});
157+
158+
test("renderMessageToStdout renders system summary messages", () => {
159+
const msg = makeSessionMessage({
160+
role: "system",
161+
content: "",
162+
meta: { isSummary: true },
163+
});
164+
const output = renderMessageToStdout(msg, RawMode.Raw);
165+
assert.ok(output.includes("(conversation summary inserted)"));
166+
});
167+
168+
test("renderMessageToStdout returns empty for unknown system messages", () => {
169+
const msg = makeSessionMessage({ role: "system", content: "" });
170+
assert.equal(renderMessageToStdout(msg, RawMode.Raw), "");
171+
});
172+
173+
// --- getUpdatePlanPreviewLines tests ---
174+
175+
test("getUpdatePlanPreviewLines returns empty for failed tool", () => {
176+
const summary: ToolSummary = { name: "UpdatePlan", params: "", ok: false, metadata: { plan: "Step 1" } };
177+
assert.deepEqual(getUpdatePlanPreviewLines(summary), []);
178+
});
179+
180+
test("getUpdatePlanPreviewLines returns empty for non-UpdatePlan tool", () => {
181+
const summary: ToolSummary = { name: "edit", params: "", ok: true, metadata: { plan: "Step 1" } };
182+
assert.deepEqual(getUpdatePlanPreviewLines(summary), []);
183+
});
184+
185+
test("getUpdatePlanPreviewLines returns empty for missing plan metadata", () => {
186+
const summary: ToolSummary = { name: "UpdatePlan", params: "", ok: true, metadata: null };
187+
assert.deepEqual(getUpdatePlanPreviewLines(summary), []);
188+
});
189+
190+
test("getUpdatePlanPreviewLines returns empty for empty plan string", () => {
191+
const summary: ToolSummary = { name: "UpdatePlan", params: "", ok: true, metadata: { plan: "" } };
192+
assert.deepEqual(getUpdatePlanPreviewLines(summary), []);
193+
});
194+
195+
test("getUpdatePlanPreviewLines extracts plan lines and filters empty rows", () => {
196+
const summary: ToolSummary = {
197+
name: "UpdatePlan",
198+
params: "",
199+
ok: true,
200+
metadata: { plan: "Step 1: Analyze\n\nStep 2: Implement\n \nStep 3: Test" },
201+
};
202+
assert.deepEqual(getUpdatePlanPreviewLines(summary), ["Step 1: Analyze", "Step 2: Implement", "Step 3: Test"]);
203+
});
204+
205+
// --- parseToolPayload tests ---
206+
207+
test("parseToolPayload returns defaults for null content", () => {
208+
const result = parseToolPayload(null);
209+
assert.deepEqual(result, { name: null, ok: true, metadata: null });
210+
});
211+
212+
test("parseToolPayload returns defaults for invalid JSON", () => {
213+
const result = parseToolPayload("not valid json");
214+
assert.deepEqual(result, { name: null, ok: true, metadata: null });
215+
});
216+
217+
test("parseToolPayload parses valid JSON with name/ok/metadata", () => {
218+
const result = parseToolPayload(JSON.stringify({ name: "read", ok: true, metadata: { file: "src/index.ts" } }));
219+
assert.deepEqual(result, { name: "read", ok: true, metadata: { file: "src/index.ts" } });
220+
});
221+
222+
test("parseToolPayload respects ok: false", () => {
223+
const result = parseToolPayload(JSON.stringify({ name: "bash", ok: false, metadata: null }));
224+
assert.deepEqual(result, { name: "bash", ok: false, metadata: null });
225+
});
226+
227+
test("parseToolPayload trims whitespace from name", () => {
228+
const result = parseToolPayload(JSON.stringify({ name: " read ", ok: true }));
229+
assert.equal(result.name, "read");
230+
});

0 commit comments

Comments
 (0)