test: PenCanvas widget test — live drawing path, device-independent
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Pumps PenCanvas with a plain Container as the page widget (no pdfrx/pdfium) and
simulates gestures to verify the INTEGRATED live path end-to-end: a stylus drag
commits a multi-point stroke; a single finger is rejected when finger-drawing is
off but draws when on; a 2nd pointer cancels an in-progress stylus stroke
(pinch/palm); committed strokes render through the revision-gated render cache
(eca5141) with no exception. Real behavioral evidence for the arbiter + render
swap beyond static analysis — without a device.
flutter analyze lib/editor clean; 233/233 tests (+5 widget).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
165
test/pen_canvas_widget_test.dart
Normal file
165
test/pen_canvas_widget_test.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
// Widget test of the INTEGRATED live drawing path (device-independent: uses a
|
||||||
|
// plain Container as the page widget, so no pdfrx/pdfium). Exercises the input
|
||||||
|
// arbiter + stroke lifecycle + the revision-gated render-cache swap (eca5141)
|
||||||
|
// end-to-end via simulated stylus / finger / pinch gestures.
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Widget host({
|
||||||
|
required List<PenStroke> strokes,
|
||||||
|
required void Function(PenStroke) onComplete,
|
||||||
|
required TransformationController controller,
|
||||||
|
bool allowFinger = false,
|
||||||
|
CanvasTool tool = CanvasTool.pen,
|
||||||
|
}) {
|
||||||
|
return 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: tool,
|
||||||
|
color: const Color(0xFF000000),
|
||||||
|
strokeWidth: 0.004,
|
||||||
|
allowFingerDrawing: allowFinger,
|
||||||
|
onStrokeComplete: onComplete,
|
||||||
|
onEraseStroke: (_, __) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('a stylus drag produces a committed stroke', (tester) async {
|
||||||
|
final committed = <PenStroke>[];
|
||||||
|
final controller = TransformationController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(host(
|
||||||
|
strokes: const [],
|
||||||
|
onComplete: committed.add,
|
||||||
|
controller: controller,
|
||||||
|
));
|
||||||
|
|
||||||
|
final center = tester.getCenter(find.byType(PenCanvas));
|
||||||
|
final g = await tester.startGesture(center,
|
||||||
|
kind: PointerDeviceKind.stylus);
|
||||||
|
await g.moveBy(const Offset(20, 10));
|
||||||
|
await g.moveBy(const Offset(20, 30));
|
||||||
|
await g.moveBy(const Offset(10, 20));
|
||||||
|
await g.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(committed, hasLength(1));
|
||||||
|
expect(committed.single.points.length, greaterThan(1),
|
||||||
|
reason: 'a multi-segment drag is not a single dot');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('a single finger does NOT draw when finger-drawing is off',
|
||||||
|
(tester) async {
|
||||||
|
final committed = <PenStroke>[];
|
||||||
|
final controller = TransformationController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(host(
|
||||||
|
strokes: const [],
|
||||||
|
onComplete: committed.add,
|
||||||
|
controller: controller,
|
||||||
|
allowFinger: false,
|
||||||
|
));
|
||||||
|
|
||||||
|
final center = tester.getCenter(find.byType(PenCanvas));
|
||||||
|
final g = await tester.startGesture(center, kind: PointerDeviceKind.touch);
|
||||||
|
await g.moveBy(const Offset(30, 30));
|
||||||
|
await g.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(committed, isEmpty, reason: 'palm/finger rejection');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('a finger DOES draw when finger-drawing is on', (tester) async {
|
||||||
|
final committed = <PenStroke>[];
|
||||||
|
final controller = TransformationController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(host(
|
||||||
|
strokes: const [],
|
||||||
|
onComplete: committed.add,
|
||||||
|
controller: controller,
|
||||||
|
allowFinger: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
final center = tester.getCenter(find.byType(PenCanvas));
|
||||||
|
final g = await tester.startGesture(center, kind: PointerDeviceKind.touch);
|
||||||
|
await g.moveBy(const Offset(30, 30));
|
||||||
|
await g.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(committed, hasLength(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('a second pointer cancels an in-progress stylus stroke (pinch)',
|
||||||
|
(tester) async {
|
||||||
|
final committed = <PenStroke>[];
|
||||||
|
final controller = TransformationController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(host(
|
||||||
|
strokes: const [],
|
||||||
|
onComplete: committed.add,
|
||||||
|
controller: controller,
|
||||||
|
));
|
||||||
|
|
||||||
|
final center = tester.getCenter(find.byType(PenCanvas));
|
||||||
|
final g1 = await tester.startGesture(center, kind: PointerDeviceKind.stylus);
|
||||||
|
await g1.moveBy(const Offset(10, 10));
|
||||||
|
// A second finger lands → the stroke must be discarded (pinch/palm).
|
||||||
|
final g2 = await tester.startGesture(
|
||||||
|
center + const Offset(60, 0), kind: PointerDeviceKind.touch);
|
||||||
|
await g1.moveBy(const Offset(10, 10));
|
||||||
|
await g1.up();
|
||||||
|
await g2.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(committed, isEmpty, reason: '2nd pointer cancels the stroke');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders committed strokes through the render cache without error',
|
||||||
|
(tester) async {
|
||||||
|
final controller = TransformationController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
final strokes = [
|
||||||
|
PenStroke(
|
||||||
|
points: const [PenPoint(0.1, 0.1, 0.5), PenPoint(0.4, 0.5, 0.6)],
|
||||||
|
color: 0xFF000000,
|
||||||
|
width: 0.004,
|
||||||
|
kind: PenStrokeKind.pen,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await tester.pumpWidget(host(
|
||||||
|
strokes: strokes,
|
||||||
|
onComplete: (_) {},
|
||||||
|
controller: controller,
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Built + painted the StaticInkPainter (render cache) with no exception.
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(find.byType(PenCanvas), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user