openpencil/tests/engine/undo-batch-idle.test.ts
Danila Poyarkov a2a5376b74 refactor(vue): extract useUndoBatch composable and add batch tests
- Move idle-timer batch logic from PropertyListRoot into reusable useUndoBatch composable
- Add unit tests for batch undo behavior (collapse, flush, redo, no-op)
2026-04-13 09:46:09 +03:00

108 lines
2.6 KiB
TypeScript

import { describe, test, expect } from 'bun:test'
import { UndoManager } from '@open-pencil/core'
describe('UndoManager idle-timer batching', () => {
function noop() {}
function entry(label: string, forward = noop, inverse = noop) {
return { label, forward, inverse }
}
test('rapid pushes inside a batch produce a single undo entry', () => {
const undo = new UndoManager()
let value = 0
undo.beginBatch('drag color')
for (let i = 1; i <= 10; i++) {
const prev = value
const next = i
undo.push({ label: `step ${i}`, forward: () => { value = next }, inverse: () => { value = prev } })
value = next
}
undo.commitBatch()
expect(value).toBe(10)
expect(undo.undoLabel).toBe('drag color')
undo.undo()
expect(value).toBe(0)
expect(undo.canUndo).toBe(false)
})
test('changing batch key flushes the previous batch', () => {
const undo = new UndoManager()
let a = 0
let b = 0
undo.beginBatch('batch A')
undo.push(entry('a1', () => { a = 1 }, () => { a = 0 }))
a = 1
undo.push(entry('a2', () => { a = 2 }, () => { a = 1 }))
a = 2
undo.commitBatch()
undo.beginBatch('batch B')
undo.push(entry('b1', () => { b = 10 }, () => { b = 0 }))
b = 10
undo.commitBatch()
expect(undo.undoLabel).toBe('batch B')
undo.undo()
expect(b).toBe(0)
expect(undo.undoLabel).toBe('batch A')
undo.undo()
expect(a).toBe(0)
})
test('discrete action between batches is separate undo entry', () => {
const undo = new UndoManager()
let v = 0
undo.beginBatch('drag')
undo.push(entry('d1', () => { v = 1 }, () => { v = 0 }))
v = 1
undo.push(entry('d2', () => { v = 2 }, () => { v = 1 }))
v = 2
undo.commitBatch()
undo.push(entry('add fill', () => { v = 100 }, () => { v = 2 }))
v = 100
undo.undo()
expect(v).toBe(2)
undo.undo()
expect(v).toBe(0)
})
test('batch redo replays all steps in order', () => {
const undo = new UndoManager()
const log: number[] = []
undo.beginBatch('batch')
undo.apply(entry('s1', () => log.push(1), () => log.pop()))
undo.apply(entry('s2', () => log.push(2), () => log.pop()))
undo.apply(entry('s3', () => log.push(3), () => log.pop()))
undo.commitBatch()
expect(log).toEqual([1, 2, 3])
undo.undo()
expect(log).toEqual([])
undo.redo()
expect(log).toEqual([1, 2, 3])
})
test('commitBatch with no entries is a no-op', () => {
const undo = new UndoManager()
undo.push(entry('existing', noop, noop))
undo.beginBatch('empty')
undo.commitBatch()
expect(undo.undoLabel).toBe('existing')
})
})