80 lines
2.2 KiB
Dart
80 lines
2.2 KiB
Dart
|
|
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);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|