Files
writer-work-flow/apps/web/lib/style/locateSegment.test.ts
Yaojia Wang bf39f50b2f fix(qa): 实施 4 组设计型 QA 项——规则删除/codex 角色/大纲卷过滤/文风回炉锚点
#5 规则 DELETE + id:暴露 RuleView.id(PK);新增 DELETE /projects/{id}/rules/{rule_id}
  (项目/规则不存在→404,成功→204,按 (id,project_id) 限定);rule_repo 加
  list_for_project/delete;RulesPage 每条加删除(乐观删+回滚+toast)。assemble 侧
  RuleView(缓存前缀)不动,列表另立 RuleListItemView。
#7 codex 角色 relations:写侧本已持久化、读端点 _existing_characters 硬编码 []。
  加 _relations_from_jsonb 解析 {name,kind,note},CodexPage 渲染关系 chip。
#8 角色入库幂等:SqlCharacterWriteRepo.create 改 (project_id,name) app 层 upsert——
  重复入库改更新而非插入;不加 UNIQUE/迁移(线上已有重复行会让约束迁移失败)。
#1 大纲卷过滤:GET /outline 支持可选 ?volume(无参=全部,向后兼容);OutlineEditor
  加「查看:全部/卷N」筛选,与生成目标卷解耦。
H3/#9 文风回炉锚点:StyleDriftSegment 加 text(逐字命中段),style.md 指示审稿输出;
  前端按内容锚点定位回炉目标(idx 仅排序),命中失败 → 提示「无法定位该段」而非
  静默 no-op。style golden fixture 已重生成。

契约变更已 pnpm gen:api(RuleView.id / DELETE rules / outline ?volume)。无迁移
(alembic 无漂移)。门禁绿:ruff/mypy(210)/alembic/pytest 760 · 前端 tsc/lint/vitest 329。
2026-06-25 12:53:03 +02:00

47 lines
1.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from "vitest";
import { locateDriftSegment } from "./locateSegment";
const DRAFT = "第一段没问题。\n\n他乘坐迈巴赫扬长而去留下一地尘土。\n\n结尾。";
const PARAS = DRAFT.split(/\n{2,}/);
describe("locateDriftSegment", () => {
it("locates by content anchor, ignoring a mismatched idx", () => {
// idx 指向第 0 段,但 text 是第 2 段——内容锚优先,定位到真实原文。
const located = locateDriftSegment(PARAS, {
idx: 0,
text: "他乘坐迈巴赫扬长而去,留下一地尘土。",
});
expect(located).toBe("他乘坐迈巴赫扬长而去,留下一地尘土。");
});
it("trims the anchor before matching", () => {
const located = locateDriftSegment(PARAS, {
idx: 99,
text: " 他乘坐迈巴赫扬长而去,留下一地尘土。 ",
});
expect(located).toBe("他乘坐迈巴赫扬长而去,留下一地尘土。");
});
it("returns null when the anchor text is not found (#9 guard — no silent no-op)", () => {
// 自带原文但终稿里已被改动——不回退到可能错位的 idx判定为定位失败。
expect(
locateDriftSegment(PARAS, { idx: 1, text: "他乘坐保时捷离开了。" }),
).toBeNull();
});
it("falls back to positional idx when no text anchor (legacy data)", () => {
expect(locateDriftSegment(PARAS, { idx: 1, text: "" })).toBe(
"他乘坐迈巴赫扬长而去,留下一地尘土。",
);
});
it("returns null when no anchor and idx is out of range (#9 guard)", () => {
expect(locateDriftSegment(PARAS, { idx: 9, text: "" })).toBeNull();
});
it("returns null when no anchor and the positional paragraph is blank", () => {
expect(locateDriftSegment(["", " "], { idx: 0, text: "" })).toBeNull();
});
});