import 'package:flutter_test/flutter_test.dart'; import 'package:badnote/models/ink_point.dart'; import 'package:badnote/models/ink_stroke.dart'; import 'package:badnote/models/pen_tool.dart'; import 'package:badnote/services/undo_manager.dart'; InkStroke _stroke(String id) => InkStroke( id: id, points: const [ InkPoint(x: 0, y: 0, pressure: 0.5, tilt: 0, timestamp: 0), InkPoint(x: 1, y: 1, pressure: 0.5, tilt: 0, timestamp: 1), ], tool: PenTool.pen, createdAt: DateTime(2024, 1, 1), ); void main() { group('UndoManager', () { test('starts empty with no undo/redo available', () { final m = UndoManager(); expect(m.currentStrokes, isEmpty); expect(m.canUndo, isFalse); expect(m.canRedo, isFalse); }); test('addStroke then undo/redo round-trips the stroke', () { final m = UndoManager(); final s = _stroke('a'); m.addStroke(s); expect(m.currentStrokes.map((e) => e.id), ['a']); expect(m.canUndo, isTrue); m.undo(); expect(m.currentStrokes, isEmpty); expect(m.canRedo, isTrue); m.redo(); expect(m.currentStrokes.map((e) => e.id), ['a']); }); test('a new stroke clears the redo stack', () { final m = UndoManager(); m.addStroke(_stroke('a')); m.undo(); expect(m.canRedo, isTrue); m.addStroke(_stroke('b')); expect(m.canRedo, isFalse); expect(m.currentStrokes.map((e) => e.id), ['b']); }); test('removeStroke with replacements (partial erase) is reversible', () { final m = UndoManager(); m.addStroke(_stroke('whole')); m.removeStroke( _stroke('whole'), replacements: [_stroke('part1'), _stroke('part2')], ); expect(m.currentStrokes.map((e) => e.id), ['part1', 'part2']); // Undo restores the original and drops the replacements. m.undo(); expect(m.currentStrokes.map((e) => e.id), ['whole']); // Redo re-applies the erase. m.redo(); expect(m.currentStrokes.map((e) => e.id), ['part1', 'part2']); }); test('currentStrokes is an unmodifiable view', () { final m = UndoManager(); m.addStroke(_stroke('a')); expect(() => m.currentStrokes.add(_stroke('b')), throwsUnsupportedError); }); }); }