189 lines
3.3 KiB
TypeScript
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')
|
|
})
|
|
})
|