test: behavioral regression guard for the absolute-snapshot pinch fix (6829076)
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Device-independent pinch test over PenCanvas: a steady two-finger spread must
grow the shared transform scale MONOTONICALLY, with no frame popping above the
final scale and snapping back (the flicker invariant the absolute-from-snapshot
rewrite enforces). Plus: a single stylus drag never pans/zooms (the recognizer
excludes stylus) — the transform stays identity while the pen draws.
Locks in 6829076 + the arbiter exclusion as regressions guards without a device.
flutter analyze lib/editor clean; 235/235 tests (+2 widget).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
96
test/pen_zoom_widget_test.dart
Normal file
96
test/pen_zoom_widget_test.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 <PenStroke>[],
|
||||
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 = <double>[];
|
||||
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));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user