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