docs(tests): explain legacy canvas and workspace unit coverage

This commit is contained in:
2026-11-12 11:51:14 +08:00
parent 6ad003eb37
commit 412e31e898
13 changed files with 154 additions and 0 deletions
+14
View File
@@ -1,16 +1,23 @@
/**
* 旧 frameId 分区成员模型的拖入、随动与恢复测试;不是 headless ownerCanvasId 树或 linked-board 深子 canvas 的全部规则。
* 成员关系由明确 drop 决定,几何重叠本身不代表已经加入分区。
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { canvasReducer, decodeCanvas, emptyCanvas, makeCanvasNode } from "../src/lib/canvas.ts";
import { assignFrameMembership, frameDropFeedback } from "../src/lib/canvas-frames.ts";
// frame、位于内部但尚未登记的 member,以及外部 icon;可分别检查 drop 准入和已有成员随动。
function seeded() {
const frame = makeCanvasNode("frame", { x: 100, y: 100 });
const member = makeCanvasNode("text", { x: 160, y: 160 });
const outsider = makeCanvasNode("icon", { x: 900, y: 120 });
return { frame, member, outsider, document: { ...emptyCanvas(), nodes: [frame, member, outsider] } };
}
// 把给定快照包成空历史结构,保持所有测试通过相同 reducer 入口。
function state(document) { return { past: [], present: document, future: [] }; }
// 多种内容 drop 到重叠 frame 时预览应与最终成员选择一致;较小 frame 优先,移到外部则同时显示和执行 detach。
test("drop previews match actual assignment for every content type, exits and overlapping frames", () => {
const { frame } = seeded();
const smaller = makeCanvasNode("frame", { x: 120, y: 120 }); smaller.width = 300; smaller.height = 240;
@@ -26,6 +33,7 @@ test("drop previews match actual assignment for every content type, exits and ov
}
});
// 只给 dropped IDs 分配成员,拖 frame 或其他节点不能顺手收集原本重叠的内容。
test("only dropped content joins a frame; pre-existing overlapping content stays outside", () => {
const { frame, member, outsider, document } = seeded();
assert.equal(document.nodes[1].data.frameId, undefined);
@@ -38,6 +46,7 @@ test("only dropped content joins a frame; pre-existing overlapping content stays
assert.equal(assignFrameMembership([frame, member, outsider], [member.id])[2].data.frameId, undefined);
});
// 已有成员随 frame 同量移动,旁边 outsider 不改位置和归属;undo 恢复成员坐标与 frameId。
test("frames carry existing members without collecting newly overlapped content and undo restores positions", () => {
const { frame, member, outsider, document } = seeded();
member.data.frameId = frame.id;
@@ -51,6 +60,7 @@ test("frames carry existing members without collecting newly overlapped content
assert.equal(history.present.nodes[1].data.frameId, frame.id);
});
// 已有成员拖出清除 frameId,落进另一 frame 则换归属;以同一位置比较两种节点集合。
test("dragging out detaches content and dragging between frames reassigns membership", () => {
const { frame, member } = seeded();
member.data.frameId = frame.id;
@@ -60,6 +70,7 @@ test("dragging out detaches content and dragging between frames reassigns member
assert.equal(assignFrameMembership([frame, second, member], [member.id])[2].data.frameId, second.id);
});
// 同时上报父 frame 和成员的最终位置时,不能再把父位移叠加到成员,避免双重移动。
test("multi-selection does not move frame members twice", () => {
const { frame, member, document } = seeded();
member.data.frameId = frame.id;
@@ -70,6 +81,7 @@ test("multi-selection does not move frame members twice", () => {
assert.deepEqual(result.present.nodes[1].position, { x: 260, y: 260 });
});
// 旧模型删除 frame 保留内容并清成员关联,undo 可恢复原 frameId;这不是 headless 删除 owning 子树的产品规则。
test("deleting a frame keeps its content and undo restores membership", () => {
const { frame, member, document } = seeded();
member.data.frameId = frame.id;
@@ -80,6 +92,7 @@ test("deleting a frame keeps its content and undo restores membership", () => {
assert.equal(canvasReducer(removed, { type: "undo" }).present.nodes[1].data.frameId, frame.id);
});
// hidden/fixed/scaled 标签、名称与 frameId 往返保存,frame 渲染层序为 -1,设置历史可撤销。
test("all label modes, frame names and memberships survive save/restore and settings undo", () => {
const { frame, member, document } = seeded();
member.data.frameId = frame.id;
@@ -95,6 +108,7 @@ test("all label modes, frame names and memberships survive save/restore and sett
}
});
// 缺失 frame、frame 作为别的内容成员、非法标签和 frame 旋转均拒绝;限制属于本旧格式。
test("invalid frame references, label modes, frame nesting and rotation are rejected", () => {
for (const mutate of [
({ member }) => { member.data.frameId = "missing"; },