feat(ux): R1+R2 审稿裁决人体学 — 冲突卡内联片段 + 分组/排序/跳下一条/主次按钮
R1: 新 lib/review/snippet.ts 从 where 解析段号取终稿命中段预览,ConflictCard 渲染引用块。 R2: 新 lib/review/grouping.ts 按 type 分组+严重度排序(仅改显示顺序,保留原始 index 守 conflict_index 不变量),组计数徽标、跳到下一条未裁决(环回)、采纳改法主按钮/忽略手改次级。 TDD: snippet 8 测 + grouping 10 测。前端门禁绿: lint/tsc/vitest 201/build。
This commit is contained in:
89
apps/web/lib/review/grouping.test.ts
Normal file
89
apps/web/lib/review/grouping.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
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 };
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user