- 冲突「采纳改法」:有补丁瞬时改入终稿并选中;无补丁但可定位则采纳时调 AI(refiner)重写该段套入;定位不到提示手改 - 正文改为行内高亮叠层编辑器(与页面同底色、随内容增高、无滚动条),点冲突卡/锚点整页滚到并选中+闪烁 - 仅对可在正文定位的冲突显示「跳转」/锚点;locate 支持从 where 引号抽原文,杜绝定位失败刷屏 - 伏笔建议卡:新埋一键登记(预填表单)/ 疑似回收一键标记 CLOSED(复用现有端点) - 审稿页草稿自动保存(防抖 PUT /draft),与「验收本章」解耦,刷新不丢 - 写作路由带章号 ?chapter=N + 验收成功面板/大纲页「写下一章」入口;写作目录从大纲列出全部章节 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { emptyDecisions, setVerdict } from "./decisions";
|
|
import {
|
|
displayOrder,
|
|
groupConflicts,
|
|
nextUnresolvedInOrder,
|
|
} from "./grouping";
|
|
import type { ReviewConflict } from "./sse";
|
|
|
|
function conflict(type: string, suggestion = "x"): ReviewConflict {
|
|
return {
|
|
type,
|
|
where: "",
|
|
refs: [],
|
|
suggestion,
|
|
original: null,
|
|
replacement: null,
|
|
};
|
|
}
|
|
|
|
describe("groupConflicts", () => {
|
|
it("groups by type and carries the original index", () => {
|
|
const conflicts = [
|
|
conflict("性格漂移", "a"),
|
|
conflict("设定违例", "b"),
|
|
conflict("性格漂移", "c"),
|
|
];
|
|
const groups = groupConflicts(conflicts);
|
|
const drift = groups.find((g) => g.type === "性格漂移");
|
|
expect(drift?.items.map((it) => it.index)).toEqual([0, 2]);
|
|
expect(drift?.items.map((it) => it.conflict.suggestion)).toEqual(["a", "c"]);
|
|
});
|
|
|
|
it("orders groups by severity (设定违例 before 性格漂移)", () => {
|
|
const groups = groupConflicts([
|
|
conflict("性格漂移"),
|
|
conflict("设定违例"),
|
|
]);
|
|
expect(groups.map((g) => g.type)).toEqual(["设定违例", "性格漂移"]);
|
|
});
|
|
|
|
it("places unknown types after the known five", () => {
|
|
const groups = groupConflicts([conflict("排版错误"), conflict("能力不符")]);
|
|
expect(groups.map((g) => g.type)).toEqual(["能力不符", "排版错误"]);
|
|
});
|
|
|
|
it("returns an empty array for no conflicts", () => {
|
|
expect(groupConflicts([])).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("displayOrder", () => {
|
|
it("flattens grouped indices in display order", () => {
|
|
const groups = groupConflicts([
|
|
conflict("性格漂移"),
|
|
conflict("设定违例"),
|
|
conflict("性格漂移"),
|
|
]);
|
|
// 设定违例(index 1) 先,再 性格漂移(0,2)
|
|
expect(displayOrder(groups)).toEqual([1, 0, 2]);
|
|
});
|
|
});
|
|
|
|
describe("nextUnresolvedInOrder", () => {
|
|
const order = [1, 0, 2];
|
|
|
|
it("finds the first unresolved when nothing is focused", () => {
|
|
const drafts = emptyDecisions(3);
|
|
expect(nextUnresolvedInOrder(order, drafts, null)).toBe(1);
|
|
});
|
|
|
|
it("advances to the next unresolved after the focused index", () => {
|
|
let drafts = emptyDecisions(3);
|
|
drafts = setVerdict(drafts, 1, "accept");
|
|
expect(nextUnresolvedInOrder(order, drafts, 1)).toBe(0);
|
|
});
|
|
|
|
it("wraps around to earlier unresolved items", () => {
|
|
let drafts = emptyDecisions(3);
|
|
drafts = setVerdict(drafts, 2, "ignore");
|
|
expect(nextUnresolvedInOrder(order, drafts, 2)).toBe(1);
|
|
});
|
|
|
|
it("returns null when all are resolved", () => {
|
|
let drafts = emptyDecisions(3);
|
|
drafts = setVerdict(drafts, 0, "accept");
|
|
drafts = setVerdict(drafts, 1, "accept");
|
|
drafts = setVerdict(drafts, 2, "accept");
|
|
expect(nextUnresolvedInOrder(order, drafts, null)).toBeNull();
|
|
});
|
|
|
|
it("returns null for an empty order", () => {
|
|
expect(nextUnresolvedInOrder([], [], null)).toBeNull();
|
|
});
|
|
});
|