// test/undo_stack_test.dart // // Pure unit tests for UndoStack. No widgets, no DB, no Flutter framework. import 'package:flutter_test/flutter_test.dart'; import 'package:badnote/editor/engine/undo_stack.dart'; void main() { group('UndoStack', () { // ------------------------------------------------------------------ basics test('starts empty with canUndo=false and canRedo=false', () { final stack = UndoStack(); expect(stack.canUndo, isFalse); expect(stack.canRedo, isFalse); }); test('undo on empty stack returns null', () { final stack = UndoStack(); expect(stack.undo(42), isNull); }); test('redo on empty stack returns null', () { final stack = UndoStack(); expect(stack.redo(), isNull); }); // -------------------------------------------- record / undo / redo cycle test('record A, B, C then undo yields B then A', () { final stack = UndoStack(); // Simulate: current starts at A=1 // Before mutating to B=2 we record the pre-mutation state A=1 stack.record(1); // snapshot before →2 // Before mutating to C=3 we record the pre-mutation state B=2 stack.record(2); // snapshot before →3 // Current live state is C=3 expect(stack.canUndo, isTrue); expect(stack.canRedo, isFalse); // First undo: push current(3) to redo, pop B(2) from undo expect(stack.undo(3), equals(2)); expect(stack.canUndo, isTrue); expect(stack.canRedo, isTrue); // Second undo: push current(2) to redo, pop A(1) from undo expect(stack.undo(2), equals(1)); expect(stack.canUndo, isFalse); expect(stack.canRedo, isTrue); }); test('redo after undo restores B', () { final stack = UndoStack(); stack.record(1); stack.record(2); stack.undo(3); // restore →2 stack.undo(2); // restore →1 // First redo: pop 2 from redo, push back to undo expect(stack.redo(), equals(2)); expect(stack.canRedo, isTrue); // Second redo: pop 3 from redo expect(stack.redo(), equals(3)); expect(stack.canRedo, isFalse); }); // ------------------------------------------ record after undo clears redo test('record after undo clears the redo stack', () { final stack = UndoStack(); stack.record(1); // before →2 stack.record(2); // before →3 stack.undo(3); // back to 2, redo has [3] expect(stack.canRedo, isTrue); // Now record a NEW snapshot (branching from current=2) stack.record(2); // before →99 // Redo stack must be cleared expect(stack.canRedo, isFalse); expect(stack.canUndo, isTrue); }); // ------------------------------------------------ capacity / cap behavior test('cap drops oldest snapshots beyond capacity', () { const cap = 3; final stack = UndoStack(cap: cap); // Record 4 snapshots — only the last 3 should survive stack.record(10); stack.record(20); stack.record(30); stack.record(40); // oldest (10) dropped // Undo 3 times from current=50 expect(stack.undo(50), equals(40)); // most recent expect(stack.undo(40), equals(30)); expect(stack.undo(30), equals(20)); // Stack is now empty — 10 was dropped expect(stack.canUndo, isFalse); }); // --------------------------------------------------- clear test('clear empties both stacks', () { final stack = UndoStack(); stack.record(1); stack.record(2); stack.undo(3); stack.clear(); expect(stack.canUndo, isFalse); expect(stack.canRedo, isFalse); }); // ---------------------------------------- works with List snapshots test('works with List snapshots (immutable caller discipline)', () { final stack = UndoStack>(); final a = List.unmodifiable([1, 2]); final b = List.unmodifiable([1, 2, 3]); stack.record(a); // before mutation to b // live state is now b final restored = stack.undo(b); expect(restored, equals(a)); expect(stack.canUndo, isFalse); expect(stack.canRedo, isTrue); }); // ------------------------------------------ redo pushes back to undo test('redo snapshot is re-undoable (undo stack grows back)', () { final stack = UndoStack(); // undo=[1], redo=[] stack.record(1); // before →2 // undo=[], redo=[2] stack.undo(2); // live=1 // undo=[2], redo=[] stack.redo(); // live=2 // canUndo must be true because redo() pushed 2 back onto undo expect(stack.canUndo, isTrue); expect(stack.canRedo, isFalse); // Undoing from a new live state 3 pops 2 off undo expect(stack.undo(3), equals(2)); }); }); }