补齐 UI 编辑器整树渲染定位验收并收口六条验收

- tests/previewWorkspaceZoom.test.tsx 新增「树层按 root.offset.min 定位,原图与子节点随之一起移动」:断言树层 style.left/top 等于 root.offset.min 且根与子节点都挂在该层
- 变异验证:层偏移兜底改成 [0,0] 后只有该用例变红(1 failed / 9 passed)
- 里程碑第 2 条勾选,6 条验收全部有变异验证过的自动化证据(当前四套 77 passed);实施计划同步更新,剩余只有真机目视验收
This commit is contained in:
kdletters
2026-09-28 17:40:21 +08:00
parent beaa6265da
commit 3ea6c992bc
2 changed files with 31 additions and 3 deletions
@@ -287,4 +287,32 @@ describe('PreviewWorkspace quick zoom', () => {
);
expect(logicalViewportScale(joint.container)).toBe(jointScale);
});
it('树层按 root.offset.min 定位,原图与子节点随之一起移动', () => {
const child: UiNode = {
...structuredClone(root),
id: 'child',
offset: { min: [10, 20], max: [210, 220] },
};
const movedRoot: UiNode = {
...structuredClone(root),
offset: { min: [40, 60], max: [1240, 860] },
children: [child],
};
const view = render(
<PreviewWorkspace
canvas={createCanvas({
uiTrees: [{ src_ui_design: 'page-1', root: movedRoot }],
})}
/>,
);
const layer = view.container.querySelector(
'[data-tree-id="page-1"]',
) as HTMLElement;
expect(layer.style.left).toBe('40px');
expect(layer.style.top).toBe('60px');
// 原图与全部子节点都挂在这一层里:整树平移由这一层统一承担,子节点不必各自改坐标。
expect(layer.querySelector('[data-node-id="root"]')).toBeTruthy();
expect(layer.querySelector('[data-node-id="child"]')).toBeTruthy();
});
});
@@ -34,7 +34,7 @@ UI 编辑器预览同时展示全部界面树。每棵树使用 root.offset 的
## 验收标准
- [x] 新树默认位于所有现有树右侧并与最小 top 对齐。
- [ ] 原图与组件随 root 一起移动,拖动结束进入 undo/redo 并可保存恢复。
- [x] 原图与组件随 root 一起移动,拖动结束进入 undo/redo 并可保存恢复。
- [x] root 不显示 resize handles,子节点 resize 不回归。
- [x] 三个开关默认 showFrame=true、showOriginImage=true、showComponent=false,且互相独立。
- [x] 预览选择不修改 activeImageId;左侧图片选择和 Inspector 既有切换保持。
@@ -53,10 +53,10 @@ UI 编辑器预览同时展示全部界面树。每棵树使用 root.offset 的
- 定向用例:`npx vitest run tests/uiEditorPreview.test.tsx tests/previewWorkspaceZoom.test.tsx tests/previewRightPanGesture.test.ts tests/uiEditorPage.test.ts --root apps/ai-game-creator-shell` → **59 passed / 0 failed**。
- 本轮补齐的验收项(第 1 条):新增 `tests/uiEditorState.test.ts` 的「新树默认落在所有现有树右侧并与最小 top 对齐」——直接跑 `createTree` 入口:第一棵落在 `[0,0]`,第二棵落在「第一棵右边界 + `UI_TREE_PADDING`」且 top 与现有最小 top 对齐;把第二棵整体下移后,第三棵仍然按**最小** top(而不是最后一棵的 top)对齐,横向取所有树最右边界。变异验证:把 `deriveTreeOffset` 的 `minY` 改成取最后一棵树的 top 后,只有这条用例变红(1 failed / 17 passed),改回后恢复绿;本轮四套 UI 编辑器用例合计 **72 passed**。
- 本轮补齐的验收项(第 2 条的两句):`tests/uiEditorPage.test.ts` 新增「整树拖动写回一个历史条目,保存后能原样读回」——`canvas.setTreeOffset('page-1', [24, 36])` 后断言状态更新且 `history.canUndo` 为真,`undo()` 回到拖动前的 `offset`、`redo()` 再回到 `[24, 36]`,最后 `save.save()` 落盘并断言写入快照的 `state.ui_trees[0].root.offset.min` 就是新值(下一次加载读回的就是这份 state)。变异验证:把 `setTreeOffset` 里的 `commit(result.state)` 换成 `applyState(result.state)`(改状态但不进历史)后,只有这条用例在 `canUndo` 处变红(1 failed / 37 passed)。「拖动结束才写回一次、指针取消不写」由既有 `previewRightPanDrag.test.tsx` 的 `commits the tree offset on pointerup but not on pointercancel` 与 `drops an in-flight tree drag when the window loses focus` 覆盖。
- 第 2 条还剩一句未单独断言:「原图与组件随 root 一起移动」。缺口很具体——jsdom 里 `getBoundingClientRect` 全为 0,不能直接比绝对坐标;要靠断言树层容器的 `style.left/top` 等于 `root.offset.min`、且根节点是该容器的子节点(从而原图与全部子节点一起随 root 平移)来钉住。
- 本轮补齐的验收项(第 2 条最后一句):「原图与组件随 root 一起移动」——`tests/previewWorkspaceZoom.test.tsx` 新增「树层按 root.offset.min 定位,原图与子节点随之一起移动」:树根 offset 为 `[40, 60]` 时 `[data-tree-id="page-1"]` 层的 `style.left/top` 必须是 `40px`/`60px`,且根节点与子节点都挂在这一层里(整树平移由这一层统一承担,子节点不必各自改坐标)。jsdom 的 `getBoundingClientRect` 全为 0,所以断言的是层坐标 + 从属关系,而不是绝对像素。变异验证:把层偏移的兜底从 `item.root.offset.min` 改成 `[0, 0]` 后只有这条用例变红(1 failed / 9 passed)。
- 「原图与组件随 root 一起移动,拖动结束进入 undo/redo 并可保存恢复」:现有 `uiEditorState.test.ts` 只覆盖 `set-tree-offset` 命令本身,没有断言预览渲染跟随 root 位移与拖动落点进 undo/redo、保存后能读回。
- 本轮补齐的验收项(第 3 条):在同一条 `shows final-preview node frames and resize handles only when requested` 里补上 root 断言——选中 root 时一个 `/调整控制点$/` 都不出现,紧接着的对照是选中 child 时恰好 8 个。变异验证:把 handle 渲染条件里的 `!isRoot &&` 去掉后只有这一处断言变红(1 failed / 10 passed,且证明 `selectedNodeId=root` 确实进入了选中态、断言不是空转),改回后恢复绿。
- 本轮补齐的验收项(第 4 条):`tests/uiEditorPage.test.ts` 新增「默认显示框线与原图、隐藏组件,且三个开关互相独立」——渲染真实 `UiEditorPage` 后按标签取三个复选框,断言初始值 `显示框线=true` / `显示原图=true` / `显示组件=false`,再分别关框线、开组件,确认互不影响。变异验证:把 `view/ui-editor/index.tsx` 的 `showFrame` 初值改成 `false` 后只有这条用例变红(1 failed / 35 passed),改回后四套用例合计 **73 passed**。
- 本轮补齐的验收项(第 5 条):`tests/uiEditorPage.test.ts` 新增「预览里选择节点不改变左侧选中的界面图」——先用 `input.selectDesignImage('page-2')` 建立 `activeImageId`,再 `canvas.selectNode('page-1-root')`,断言 `selectedNodeId` 变了而 `activeImageId` 仍是 `page-2`;随后切到 Inspector 部件(`input.selectSprite('panel')`)同样不改。断言走 hook 暴露的 `canvas.activeImageId`,不依赖左侧列表的 CSS class(那里没有 aria 属性)。变异验证:在 `selectNode` 里塞一句 `setActiveImageId('page-1')`(模拟「按选中节点同步左侧图片」的误改)后,只有这条用例变红(1 failed / 36 passed)。
- 本轮补齐的验收项(第 6 条):`tests/previewWorkspaceZoom.test.tsx` 新增「进入时按全部树的联合边界适配,选中节点不重新适配」——两棵树分别落在 x=0 与 x=1600(两棵界面图都必须在投影里,否则 `allBounds` 会跳过它),断言联合适配后的逻辑缩放**小于**单棵树的适配值(证明首次进入用的是全部树的联合边界),再用 `selectedNodeId` 重新渲染一次并断言缩放不变(选中不该顶掉用户视口);手动适配那一半继续由既有 `refits the preview when the zoom percentage button is clicked` 覆盖。变异验证:把 `allBounds` 的过滤结果接上 `slice(0, 1)`(只适配第一棵树)后,只有这条用例的第一处断言变红(1 failed / 8 passed)。
- 仍待补:真机手感与多树布局的目视验收(本机只到定向用例级)。
- 仍待补:真机手感与多树布局的目视验收(本机只到定向用例级)。