写作工作台重构剩余 3 项(多 agent 并行构建各自新文件,主线统一集成): - WFW-5 内容感知书名(#6):buildManuscriptExcerpt 取正文首尾摘录(硬上界控 token); GeneratorRunner 加 manuscriptText,有 brief 字段时出现「用当前正文」按钮把摘录填入 brief,让 book-title 等据正文反推。纯前端零后端;HITL——只填表单,生成仍需作者触发。 - WFW-6 上下文速查抽屉(#7):ContextDrawer 视口无关右侧 slide-over,设定库/大纲完整 只读速查 + 伏笔/规则/文风摘要跳链;底栏「速查」按钮触发。加法式、CONTEXT_DRAWER_ENABLED 一键回滚、旧侧栏/全宽页路由全保留。 - WFW-7 genre 采集 + 无大纲降级:项目无题材时首次写章前弹 GenrePicker 采集(临时并入 本次 directive 不落库);chapters 为空时编辑器显式提示「尚无大纲,将按设定自由生成」。 各项 TDD:manuscriptExcerpt/useGenreGate/contextDrawer/useContextDrawer 共 32 新单测。 门禁全绿:vitest 629 / tsc / lint / build / coverage 95.43%。
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
|
||
import {
|
||
buildManuscriptExcerpt,
|
||
DEFAULT_EXCERPT_MAX_CHARS,
|
||
} from "./manuscriptExcerpt";
|
||
|
||
describe("buildManuscriptExcerpt", () => {
|
||
it("短文(未超界)trim 后原样返回,无省略标记", () => {
|
||
const text = " 第一章 少年初入江湖。 ";
|
||
const excerpt = buildManuscriptExcerpt(text, 1800);
|
||
expect(excerpt).toBe("第一章 少年初入江湖。");
|
||
expect(excerpt).not.toContain("中略");
|
||
});
|
||
|
||
it("空串与纯空白 → 返回 \"\"", () => {
|
||
expect(buildManuscriptExcerpt("", 1800)).toBe("");
|
||
expect(buildManuscriptExcerpt(" \n\t ", 1800)).toBe("");
|
||
});
|
||
|
||
it("正好等于上界 → 原样返回,不截断", () => {
|
||
const text = "字".repeat(1800);
|
||
const excerpt = buildManuscriptExcerpt(text, 1800);
|
||
expect(excerpt).toBe(text);
|
||
expect(excerpt).not.toContain("中略");
|
||
});
|
||
|
||
it("超长 → 取首尾拼接、含省略标记,且首尾片段都保留", () => {
|
||
const head = "甲".repeat(1000);
|
||
const tail = "乙".repeat(1000);
|
||
const text = head + tail; // 2000 > 1800
|
||
const excerpt = buildManuscriptExcerpt(text, 1800);
|
||
|
||
expect(excerpt).toContain("中略");
|
||
// 首段来自开头、尾段来自结尾。
|
||
expect(excerpt.startsWith("甲")).toBe(true);
|
||
expect(excerpt.endsWith("乙")).toBe(true);
|
||
});
|
||
|
||
it("超长摘录长度 ≤ 上界 + 少许标记冗余(正文部分不超界)", () => {
|
||
const text = "文".repeat(5000);
|
||
const maxChars = 1800;
|
||
const excerpt = buildManuscriptExcerpt(text, maxChars);
|
||
|
||
// 省略标记之外的正文字符数不得超过上界。
|
||
const bodyLen = excerpt.replace(/……(中略)……/g, "").replace(/\s+/g, "")
|
||
.length;
|
||
expect(bodyLen).toBeLessThanOrEqual(maxChars);
|
||
// 整体长度仅比上界多出标记长度这点冗余。
|
||
expect(excerpt.length).toBeLessThanOrEqual(maxChars + 20);
|
||
});
|
||
|
||
it("默认上界常量生效(不传 maxChars)", () => {
|
||
const text = "字".repeat(DEFAULT_EXCERPT_MAX_CHARS + 500);
|
||
const excerpt = buildManuscriptExcerpt(text);
|
||
expect(excerpt).toContain("中略");
|
||
const bodyLen = excerpt.replace(/……(中略)……/g, "").replace(/\s+/g, "")
|
||
.length;
|
||
expect(bodyLen).toBeLessThanOrEqual(DEFAULT_EXCERPT_MAX_CHARS);
|
||
});
|
||
});
|