diff --git a/test/pen_eraser_widget_test.dart b/test/pen_eraser_widget_test.dart new file mode 100644 index 0000000..678eb3b --- /dev/null +++ b/test/pen_eraser_widget_test.dart @@ -0,0 +1,101 @@ +// Device-independent widget test of the eraser path (the user reported eraser +// reliability issues). A stylus drag through a committed stroke in eraser mode +// must fire onEraseStroke for that stroke. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:badnote/editor/canvas/pen_canvas.dart'; +import 'package:badnote/editor/canvas/pen_stroke.dart'; + +void main() { + const pageSize = Size(400, 600); + + // A horizontal stroke through the normalized center (0.5, 0.5). + PenStroke centerStroke() => PenStroke( + points: const [ + PenPoint(0.4, 0.5, 0.5), + PenPoint(0.5, 0.5, 0.5), + PenPoint(0.6, 0.5, 0.5), + ], + color: 0xFF000000, + width: 0.004, + kind: PenStrokeKind.pen, + ); + + Widget host({ + required List strokes, + required void Function(int, List) onErase, + required TransformationController controller, + }) => + MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: pageSize.width, + height: pageSize.height, + child: PenCanvas( + pageWidget: Container(color: const Color(0xFFEEEEEE)), + pageSize: pageSize, + strokes: strokes, + transformationController: controller, + tool: CanvasTool.eraser, + color: const Color(0xFF000000), + strokeWidth: 0.004, + onStrokeComplete: (_) {}, + onEraseStroke: onErase, + ), + ), + ), + ), + ); + + testWidgets('eraser tool: a stylus pass over a stroke fires onEraseStroke', + (tester) async { + final erased = []; + final controller = TransformationController(); + addTearDown(controller.dispose); + + await tester.pumpWidget(host( + strokes: [centerStroke()], + onErase: (i, _) => erased.add(i), + controller: controller, + )); + + // Drag a stylus vertically through the screen center (= normalized 0.5,0.5, + // where the stroke sits). + final center = tester.getCenter(find.byType(PenCanvas)); + final g = await tester.startGesture(center + const Offset(0, -20), + kind: PointerDeviceKind.stylus); + await g.moveBy(const Offset(0, 20)); + await g.moveBy(const Offset(0, 20)); + await g.up(); + await tester.pump(); + + expect(erased, contains(0), reason: 'the center stroke should be erased'); + }); + + testWidgets('eraser tool: a pass far from any stroke erases nothing', + (tester) async { + final erased = []; + final controller = TransformationController(); + addTearDown(controller.dispose); + + await tester.pumpWidget(host( + strokes: [centerStroke()], + onErase: (i, _) => erased.add(i), + controller: controller, + )); + + // Drag in the top-left corner, far from the centered stroke. + final topLeft = tester.getTopLeft(find.byType(PenCanvas)); + final g = await tester.startGesture(topLeft + const Offset(10, 10), + kind: PointerDeviceKind.stylus); + await g.moveBy(const Offset(5, 5)); + await g.up(); + await tester.pump(); + + expect(erased, isEmpty); + }); +}