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') }) })