124 lines
4.6 KiB
TypeScript
124 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
|
||
const {
|
||
buildSpeakableTerminalText,
|
||
isSpeakableTextLikelyComplete,
|
||
splitSpeakableTextForTts,
|
||
stripTerminalAnsi
|
||
} = require("./terminalSpeakableText.js");
|
||
|
||
describe("terminalSpeakableText", () => {
|
||
it("应提取最近一段自然语言并跳过命令与代码", () => {
|
||
const source = [
|
||
"$ codex ask",
|
||
"正在分析项目结构...",
|
||
"const answer = computeResult();",
|
||
"",
|
||
"请先检查 gateway 的环境变量配置。",
|
||
"确认 TTS_SECRET_ID、TTS_SECRET_KEY 与 TTS_REGION 是否一致。"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe(
|
||
"请先检查 gateway 的环境变量配置。确认 TTS_SECRET_ID、TTS_SECRET_KEY 与 TTS_REGION 是否一致。"
|
||
);
|
||
});
|
||
|
||
it("只有命令、路径和进度条时应返回空文本", () => {
|
||
const source = [
|
||
"$ npm run build",
|
||
"/Users/demo/project/src/index.ts",
|
||
"[====== ] 60%",
|
||
"spinner loading"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe("");
|
||
});
|
||
|
||
it("应跳过 Codex 输入行、状态行与底部 footer,只保留回答正文", () => {
|
||
const source = [
|
||
"› Summarize recent commits",
|
||
"Working (0s · esc to interrupt)",
|
||
"■ Conversation interrupted - tell the model what to do differently.",
|
||
"Something went wrong? Hit `/feedback` to report the issue.",
|
||
"本次修改主要收口了 TTS 播放链路。",
|
||
"同时移除了小程序侧重复下载缓存文件的绕路实现。",
|
||
"gpt-5.4 xhigh · 100% left · ~/remoteconn"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe(
|
||
"本次修改主要收口了 TTS 播放链路。同时移除了小程序侧重复下载缓存文件的绕路实现。"
|
||
);
|
||
});
|
||
|
||
it("应跳过被终端换行拆开的 footer 残片", () => {
|
||
const source = [
|
||
"本次修改已经完成。",
|
||
"42% left · ~/remoteconn"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe("本次修改已经完成。");
|
||
});
|
||
|
||
it("应去掉列表项目符号,避免 TTS 在首句前卡住", () => {
|
||
const source = [
|
||
"• 第一条消息。",
|
||
" 第二条消息。",
|
||
" 第三条消息。",
|
||
" 第四条消息。"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe("第一条消息。第二条消息。第三条消息。第四条消息。");
|
||
});
|
||
|
||
it("应去掉紧贴正文的项目符号前缀", () => {
|
||
const source = ["•第一条消息。", "•第二条消息。"].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source)).toBe("第一条消息。第二条消息。");
|
||
});
|
||
|
||
it("中间夹杂代码块时应继续向上回收同轮正文,而不是只读最后一小段", () => {
|
||
const source = [
|
||
"第一段说明,先交代修复背景。",
|
||
"第二段说明,描述影响范围。",
|
||
"```ts",
|
||
"const demo = true;",
|
||
"```",
|
||
"第三段说明,给出处理方式。",
|
||
"第四段说明,提醒重启服务。"
|
||
].join("\n");
|
||
|
||
expect(buildSpeakableTerminalText(source, { maxChars: 200 })).toBe(
|
||
"第一段说明,先交代修复背景。第二段说明,描述影响范围。第三段说明,给出处理方式。第四段说明,提醒重启服务。"
|
||
);
|
||
});
|
||
|
||
it("长中文文本应保留到总量配置上限,并支持后续分段", () => {
|
||
const source = "这是一个较长的测试输出。".repeat(40);
|
||
const result = buildSpeakableTerminalText(source);
|
||
const segments = splitSpeakableTextForTts(result);
|
||
|
||
expect(Buffer.byteLength(result, "utf8")).toBeLessThanOrEqual(1500);
|
||
expect(result.length).toBeLessThanOrEqual(500);
|
||
expect(result.length).toBeGreaterThan(0);
|
||
expect(segments.length).toBeGreaterThan(1);
|
||
segments.forEach((segment: string) => {
|
||
expect(Buffer.byteLength(segment, "utf8")).toBeLessThanOrEqual(240);
|
||
expect(segment.length).toBeLessThanOrEqual(80);
|
||
});
|
||
});
|
||
|
||
it("应支持按自定义总长度提取正文", () => {
|
||
const source = "这是一个较长的测试输出。".repeat(40);
|
||
const result = buildSpeakableTerminalText(source, { maxChars: 120 });
|
||
|
||
expect(result.length).toBeLessThanOrEqual(120);
|
||
expect(Buffer.byteLength(result, "utf8")).toBeLessThanOrEqual(360);
|
||
});
|
||
|
||
it("应支持去除 ANSI 并判断句子是否收束", () => {
|
||
expect(stripTerminalAnsi("\u001b[32m请先检查配置。\u001b[0m")).toBe("请先检查配置。");
|
||
expect(isSpeakableTextLikelyComplete("请先检查配置。")).toBe(true);
|
||
expect(isSpeakableTextLikelyComplete("请先检查配置")).toBe(false);
|
||
});
|
||
});
|