openpencil/tests/engine/editor/undo/batch-idle.test.ts
2026-05-18 18:58:30 +03:00

189 lines
3.3 KiB
TypeScript

import { describe, test, expect } from 'bun:test'
import { createUndoManager, noop, undoEntry } from '#tests/helpers/undo'
function assignValue(setValue: (value: number) => void, value: number) {
return () => setValue(value)
}
describe('UndoManager idle-timer batching', () => {
test('rapid pushes inside a batch produce a single undo entry', () => {
const undo = createUndoManager()
let value = 0
const setValue = (nextValue: number) => {
value = nextValue
}
undo.beginBatch('drag color')
for (let i = 1; i <= 10; i++) {
const prev = value
const next = i
undo.push(undoEntry(`step ${i}`, assignValue(setValue, next), assignValue(setValue, 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 = createUndoManager()
let a = 0
let b = 0
undo.beginBatch('batch A')
undo.push(
undoEntry(
'a1',
() => {
a = 1
},
() => {
a = 0
}
)
)
a = 1
undo.push(
undoEntry(
'a2',
() => {
a = 2
},
() => {
a = 1
}
)
)
a = 2
undo.commitBatch()
undo.beginBatch('batch B')
undo.push(
undoEntry(
'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 = createUndoManager()
let v = 0
undo.beginBatch('drag')
undo.push(
undoEntry(
'd1',
() => {
v = 1
},
() => {
v = 0
}
)
)
v = 1
undo.push(
undoEntry(
'd2',
() => {
v = 2
},
() => {
v = 1
}
)
)
v = 2
undo.commitBatch()
undo.push(
undoEntry(
'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 = createUndoManager()
const log: number[] = []
undo.beginBatch('batch')
undo.apply(
undoEntry(
's1',
() => log.push(1),
() => log.pop()
)
)
undo.apply(
undoEntry(
's2',
() => log.push(2),
() => log.pop()
)
)
undo.apply(
undoEntry(
'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 = createUndoManager()
undo.push(undoEntry('existing', noop, noop))
undo.beginBatch('empty')
undo.commitBatch()
expect(undo.undoLabel).toBe('existing')
})
})