- Strip tools/schema.ts from 1831 to 65 lines (remove duplicate tool defs) - Fix tools/index.ts to export ALL_TOOLS from registry instead of schema - Fix renderJsx → renderJSX typo in tools/create.ts and structure.ts - Add geometry.ts with shared rotation/coordinate math utilities - Add isArrayMixed() helper to use-multi-props.ts, deduplicate FillSection/StrokeSection/EffectsSection - Fix broken test imports: use @open-pencil/core instead of relative paths into packages/core/src/ - Merge duplicate import statements across all test files - Export profiler internals and style-run helpers from core index.ts - Add 6 new test files: color, undo, snap, vector, style-runs, text-editor (98 new tests) - Update renderer-effects test for renderer/ directory split
135 lines
3.6 KiB
TypeScript
135 lines
3.6 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
|
|
import { UndoManager } from '@open-pencil/core'
|
|
|
|
describe('UndoManager', () => {
|
|
test('initial state', () => {
|
|
const undo = new UndoManager()
|
|
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 = new UndoManager()
|
|
let value = 0
|
|
undo.apply({
|
|
label: 'increment',
|
|
forward: () => { value = 1 },
|
|
inverse: () => { value = 0 }
|
|
})
|
|
expect(value).toBe(1)
|
|
expect(undo.canUndo).toBe(true)
|
|
expect(undo.undoLabel).toBe('increment')
|
|
})
|
|
|
|
test('push does not execute forward', () => {
|
|
const undo = new UndoManager()
|
|
let value = 0
|
|
undo.push({
|
|
label: 'set',
|
|
forward: () => { value = 1 },
|
|
inverse: () => { value = 0 }
|
|
})
|
|
expect(value).toBe(0)
|
|
expect(undo.canUndo).toBe(true)
|
|
})
|
|
|
|
test('undo calls inverse and returns label', () => {
|
|
const undo = new UndoManager()
|
|
let value = 0
|
|
undo.apply({
|
|
label: 'set',
|
|
forward: () => { value = 1 },
|
|
inverse: () => { 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 = new UndoManager()
|
|
let value = 0
|
|
undo.apply({
|
|
label: 'set',
|
|
forward: () => { value = 1 },
|
|
inverse: () => { 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 = new UndoManager()
|
|
undo.apply({ label: 'a', forward: () => {}, inverse: () => {} })
|
|
undo.undo()
|
|
expect(undo.canRedo).toBe(true)
|
|
undo.apply({ label: 'b', forward: () => {}, inverse: () => {} })
|
|
expect(undo.canRedo).toBe(false)
|
|
})
|
|
|
|
test('undo on empty returns null', () => {
|
|
const undo = new UndoManager()
|
|
expect(undo.undo()).toBeNull()
|
|
})
|
|
|
|
test('redo on empty returns null', () => {
|
|
const undo = new UndoManager()
|
|
expect(undo.redo()).toBeNull()
|
|
})
|
|
|
|
test('clear empties both stacks', () => {
|
|
const undo = new UndoManager()
|
|
undo.apply({ label: 'a', forward: () => {}, inverse: () => {} })
|
|
undo.undo()
|
|
undo.clear()
|
|
expect(undo.canUndo).toBe(false)
|
|
expect(undo.canRedo).toBe(false)
|
|
})
|
|
|
|
test('batch combines into single entry', () => {
|
|
const undo = new UndoManager()
|
|
const log: number[] = []
|
|
undo.beginBatch('batch')
|
|
undo.apply({ label: 'a', forward: () => log.push(1), inverse: () => log.push(-1) })
|
|
undo.apply({ label: 'b', forward: () => log.push(2), inverse: () => 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('empty batch commits nothing', () => {
|
|
const undo = new UndoManager()
|
|
undo.beginBatch('empty')
|
|
undo.commitBatch()
|
|
expect(undo.canUndo).toBe(false)
|
|
})
|
|
|
|
test('multiple undo/redo', () => {
|
|
const undo = new UndoManager()
|
|
let val = 0
|
|
undo.apply({ label: 'to1', forward: () => { val = 1 }, inverse: () => { val = 0 } })
|
|
undo.apply({ label: 'to2', forward: () => { val = 2 }, inverse: () => { val = 1 } })
|
|
undo.apply({ label: 'to3', forward: () => { val = 3 }, inverse: () => { 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)
|
|
})
|
|
})
|