修复: op 引擎 undo 逆操作 z序/越界缺陷,补 14 项核心测试

This commit is contained in:
lxy
2026-08-24 02:13:15 +08:00
parent 01f0f45007
commit 7f08f654d9
2 changed files with 141 additions and 5 deletions
+27 -5
View File
@@ -40,6 +40,8 @@ export interface Op {
export interface HistoryEntry {
forward: Op
backward: Op
/** 批次标记:同批多条(如一次导入 N 张图)undo/redo 时整组一起回滚 */
group?: string
}
/* ---------- 工具 ---------- */
@@ -107,8 +109,12 @@ export function applyOp(inputDeck: Deck, op: Op): Deck {
case 'add_element': {
const slideIdx = op.slideIdx as number
const element = op.element as SlideElement
const atIndex = op.atIndex as number | undefined
const s = deck.slides[slideIdx]
if (s) s.elements.push(element)
if (s) {
if (atIndex != null) s.elements.splice(atIndex, 0, element) // 指定位置插入(撤销删除时回到原 z 序)
else s.elements.push(element)
}
break
}
@@ -244,18 +250,34 @@ export function invertOp(inputDeck: Deck, op: Op): Op {
}
case 'del_element': {
// 逆:把删掉的元素加回
// 逆:把删掉的元素加回原位置(保持 z 序)
const slideIdx = op.slideIdx as number
const deletedElement = op.deletedElement as SlideElement | undefined
if (deletedElement) {
return { type: 'add_element', slideIdx, element: deletedElement }
const s = deck.slides[slideIdx]
const at = s ? s.elements.findIndex(e => e.id === deletedElement.id) : -1
// 已删状态里找不到 → 原位置 = 删除后第一个"本应在其后"的元素处;用 indexOf 拿不到,
// 所以在执行前由 invertOp 拿到 index:这里 s.elements 已无该元素,取其后继位置
const insertAt = at >= 0 ? at : (s ? s.elements.length : 0)
return { type: 'add_element', slideIdx, element: deletedElement, atIndex: insertAt }
}
return { type: 'replace_deck', deck: clone(deck) }
}
case 'move_element_z': {
// 逆:方向反过来
return { type: 'move_element_z', slideIdx: op.slideIdx, elementId: op.elementId, dir: -(op.dir as number) }
// 逆:方向反过来。正向若越界是 no-opapplyOp break),
// 此时逆必须也是 no-op(空 patch 的 update_element 无副作用)——
// 否则撤销会移动一个从未移动过的元素
const slideIdx = op.slideIdx as number
const elementId = op.elementId as string
const s = deck.slides[slideIdx]
const i = s ? s.elements.findIndex(e => e.id === elementId) : -1
const j = i >= 0 ? ((op.dir as number) > 0 ? i + 1 : i - 1) : -1
const outOfRange = i < 0 || j < 0 || j >= (s ? s.elements.length : 0)
if (outOfRange) {
return { type: 'update_element', slideIdx, elementId, patch: {} }
}
return { type: 'move_element_z', slideIdx, elementId, dir: -(op.dir as number) }
}
case 'set_theme': {