- Move top-level engine and e2e prefixed test files under domain folders - Update fixture path helpers after moving render and pen tests - Add lint coverage to prevent new top-level prefixed test files - Refresh testing docs for the new fig and layout paths
321 lines
6.2 KiB
TypeScript
321 lines
6.2 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
|
|
import { createUndoManager, undoEntry } from '#tests/helpers/undo'
|
|
|
|
describe('UndoManager', () => {
|
|
test('initial state', () => {
|
|
const undo = createUndoManager()
|
|
expect(undo.canUndo).toBe(false)
|
|
expect(undo.canRedo).toBe(false)
|
|
expect(undo.undoLabel).toBeNull()
|
|
expect(undo.redoLabel).toBeNull()
|
|
})
|
|
|
|
test('apply executes forward and enables undo', () => {
|
|
const undo = createUndoManager()
|
|
let value = 0
|
|
undo.apply(
|
|
undoEntry(
|
|
'increment',
|
|
() => {
|
|
value = 1
|
|
},
|
|
() => {
|
|
value = 0
|
|
}
|
|
)
|
|
)
|
|
expect(value).toBe(1)
|
|
expect(undo.canUndo).toBe(true)
|
|
expect(undo.undoLabel).toBe('increment')
|
|
})
|
|
|
|
test('push does not execute forward', () => {
|
|
const undo = createUndoManager()
|
|
let value = 0
|
|
undo.push(
|
|
undoEntry(
|
|
'set',
|
|
() => {
|
|
value = 1
|
|
},
|
|
() => {
|
|
value = 0
|
|
}
|
|
)
|
|
)
|
|
expect(value).toBe(0)
|
|
expect(undo.canUndo).toBe(true)
|
|
})
|
|
|
|
test('undo calls inverse and returns label', () => {
|
|
const undo = createUndoManager()
|
|
let value = 0
|
|
undo.apply(
|
|
undoEntry(
|
|
'set',
|
|
() => {
|
|
value = 1
|
|
},
|
|
() => {
|
|
value = 0
|
|
}
|
|
)
|
|
)
|
|
const label = undo.undo()
|
|
expect(label).toBe('set')
|
|
expect(value).toBe(0)
|
|
expect(undo.canUndo).toBe(false)
|
|
expect(undo.canRedo).toBe(true)
|
|
})
|
|
|
|
test('redo calls forward and returns label', () => {
|
|
const undo = createUndoManager()
|
|
let value = 0
|
|
undo.apply(
|
|
undoEntry(
|
|
'set',
|
|
() => {
|
|
value = 1
|
|
},
|
|
() => {
|
|
value = 0
|
|
}
|
|
)
|
|
)
|
|
undo.undo()
|
|
const label = undo.redo()
|
|
expect(label).toBe('set')
|
|
expect(value).toBe(1)
|
|
expect(undo.canRedo).toBe(false)
|
|
})
|
|
|
|
test('new action clears redo stack', () => {
|
|
const undo = createUndoManager()
|
|
undo.apply(undoEntry('a'))
|
|
undo.undo()
|
|
expect(undo.canRedo).toBe(true)
|
|
undo.apply(undoEntry('b'))
|
|
expect(undo.canRedo).toBe(false)
|
|
})
|
|
|
|
test('undo on empty returns null', () => {
|
|
const undo = createUndoManager()
|
|
expect(undo.undo()).toBeNull()
|
|
})
|
|
|
|
test('redo on empty returns null', () => {
|
|
const undo = createUndoManager()
|
|
expect(undo.redo()).toBeNull()
|
|
})
|
|
|
|
test('clear empties both stacks', () => {
|
|
const undo = createUndoManager()
|
|
undo.apply(undoEntry('a'))
|
|
undo.undo()
|
|
undo.clear()
|
|
expect(undo.canUndo).toBe(false)
|
|
expect(undo.canRedo).toBe(false)
|
|
})
|
|
|
|
test('history limit drops oldest entries', () => {
|
|
const undo = createUndoManager({ limit: 2 })
|
|
let val = 0
|
|
undo.apply(
|
|
undoEntry(
|
|
'to1',
|
|
() => {
|
|
val = 1
|
|
},
|
|
() => {
|
|
val = 0
|
|
}
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'to2',
|
|
() => {
|
|
val = 2
|
|
},
|
|
() => {
|
|
val = 1
|
|
}
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'to3',
|
|
() => {
|
|
val = 3
|
|
},
|
|
() => {
|
|
val = 2
|
|
}
|
|
)
|
|
)
|
|
|
|
expect(undo.undoLabel).toBe('to3')
|
|
undo.undo()
|
|
undo.undo()
|
|
expect(val).toBe(1)
|
|
expect(undo.undo()).toBeNull()
|
|
})
|
|
|
|
test('batch combines into single entry', () => {
|
|
const undo = createUndoManager()
|
|
const log: number[] = []
|
|
undo.beginBatch('batch')
|
|
undo.apply(
|
|
undoEntry(
|
|
'a',
|
|
() => log.push(1),
|
|
() => log.push(-1)
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'b',
|
|
() => log.push(2),
|
|
() => log.push(-2)
|
|
)
|
|
)
|
|
undo.commitBatch()
|
|
expect(log).toEqual([1, 2])
|
|
expect(undo.undoLabel).toBe('batch')
|
|
|
|
undo.undo()
|
|
expect(log).toEqual([1, 2, -2, -1])
|
|
})
|
|
|
|
test('runBatch commits on success', () => {
|
|
const undo = createUndoManager()
|
|
const log: number[] = []
|
|
|
|
undo.runBatch('batch', () => {
|
|
undo.apply(
|
|
undoEntry(
|
|
'a',
|
|
() => log.push(1),
|
|
() => log.push(-1)
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'b',
|
|
() => log.push(2),
|
|
() => log.push(-2)
|
|
)
|
|
)
|
|
})
|
|
|
|
expect(log).toEqual([1, 2])
|
|
expect(undo.undoLabel).toBe('batch')
|
|
undo.undo()
|
|
expect(log).toEqual([1, 2, -2, -1])
|
|
})
|
|
|
|
test('runBatch rolls back on throw', () => {
|
|
const undo = createUndoManager()
|
|
const log: number[] = []
|
|
|
|
expect(() =>
|
|
undo.runBatch('batch', () => {
|
|
undo.apply(
|
|
undoEntry(
|
|
'a',
|
|
() => log.push(1),
|
|
() => log.push(-1)
|
|
)
|
|
)
|
|
throw new Error('boom')
|
|
})
|
|
).toThrow('boom')
|
|
|
|
expect(log).toEqual([1, -1])
|
|
expect(undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('nested batches commit as a single outer entry', () => {
|
|
const undo = createUndoManager()
|
|
const log: number[] = []
|
|
|
|
undo.runBatch('outer', () => {
|
|
undo.apply(
|
|
undoEntry(
|
|
'a',
|
|
() => log.push(1),
|
|
() => log.push(-1)
|
|
)
|
|
)
|
|
undo.runBatch('inner', () => {
|
|
undo.apply(
|
|
undoEntry(
|
|
'b',
|
|
() => log.push(2),
|
|
() => log.push(-2)
|
|
)
|
|
)
|
|
})
|
|
})
|
|
|
|
expect(undo.undoLabel).toBe('outer')
|
|
undo.undo()
|
|
expect(log).toEqual([1, 2, -2, -1])
|
|
})
|
|
|
|
test('empty batch commits nothing', () => {
|
|
const undo = createUndoManager()
|
|
undo.beginBatch('empty')
|
|
undo.commitBatch()
|
|
expect(undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('multiple undo/redo', () => {
|
|
const undo = createUndoManager()
|
|
let val = 0
|
|
undo.apply(
|
|
undoEntry(
|
|
'to1',
|
|
() => {
|
|
val = 1
|
|
},
|
|
() => {
|
|
val = 0
|
|
}
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'to2',
|
|
() => {
|
|
val = 2
|
|
},
|
|
() => {
|
|
val = 1
|
|
}
|
|
)
|
|
)
|
|
undo.apply(
|
|
undoEntry(
|
|
'to3',
|
|
() => {
|
|
val = 3
|
|
},
|
|
() => {
|
|
val = 2
|
|
}
|
|
)
|
|
)
|
|
|
|
undo.undo()
|
|
expect(val).toBe(2)
|
|
undo.undo()
|
|
expect(val).toBe(1)
|
|
undo.redo()
|
|
expect(val).toBe(2)
|
|
undo.redo()
|
|
expect(val).toBe(3)
|
|
})
|
|
})
|