// Behavioral test of the absolute-snapshot pinch fix (6829076) — device- // independent (no pdfrx). Simulates a two-finger pinch over PenCanvas and // samples the shared transform's scale each frame: a steady spread must grow // the scale MONOTONICALLY, never spiking above the final value and snapping // back (the reported flicker the fix removes). 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(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: const [], transformationController: controller, tool: CanvasTool.pen, color: const Color(0xFF000000), strokeWidth: 0.004, onStrokeComplete: (_) {}, onEraseStroke: (_, _) {}, ), ), ), ), ); testWidgets('a steady two-finger spread grows scale monotonically (no spike)', (tester) async { final controller = TransformationController(); addTearDown(controller.dispose); await tester.pumpWidget(host(controller)); final center = tester.getCenter(find.byType(PenCanvas)); final f1 = await tester.startGesture( center + const Offset(-20, 0), kind: PointerDeviceKind.touch); final f2 = await tester.startGesture( center + const Offset(20, 0), kind: PointerDeviceKind.touch); final scales = []; for (var i = 0; i < 8; i++) { // Spread the fingers apart symmetrically (pure zoom-in, focal steady). await f1.moveBy(const Offset(-8, 0)); await f2.moveBy(const Offset(8, 0)); await tester.pump(); scales.add(controller.value.getMaxScaleOnAxis()); } await f1.up(); await f2.up(); await tester.pump(); final maxScale = scales.reduce((a, b) => a > b ? a : b); final finalScale = scales.last; // Zoom actually happened. expect(finalScale, greaterThan(1.0)); // No frame spiked ABOVE the final scale and snapped back — the fix's // invariant. (Allow a tiny epsilon for fp.) expect(maxScale, lessThanOrEqualTo(finalScale + 1e-6), reason: 'a frame popped bigger than the final scale — flicker'); // Monotonic non-decreasing across the steady spread. for (var i = 1; i < scales.length; i++) { expect(scales[i], greaterThanOrEqualTo(scales[i - 1] - 1e-6), reason: 'scale went backwards at frame $i: $scales'); } }); testWidgets('the stylus never pans/zooms (excluded from the recognizer)', (tester) async { final controller = TransformationController(); addTearDown(controller.dispose); await tester.pumpWidget(host(controller)); final center = tester.getCenter(find.byType(PenCanvas)); // A single stylus drag must DRAW, not pan — the transform stays identity. final g = await tester.startGesture(center, kind: PointerDeviceKind.stylus); await g.moveBy(const Offset(40, 40)); await g.up(); await tester.pump(); expect(controller.value.getMaxScaleOnAxis(), closeTo(1.0, 1e-9)); expect(controller.value.getTranslation().x, closeTo(0, 1e-6)); expect(controller.value.getTranslation().y, closeTo(0, 1e-6)); }); }