From 48457d736b76725d43ee807023df52755db18b04 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Fri, 4 Nov 2022 13:13:53 -0400 Subject: [PATCH] WidgetController.startGesture trackpad support (#114631) --- packages/flutter_test/lib/src/controller.dart | 16 ++++++--- .../flutter_test/lib/src/test_pointer.dart | 32 ++++++++++++++--- .../flutter_test/test/controller_test.dart | 34 +++++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index d9eac55c8059..f7bf7e2e696a 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -1086,12 +1086,14 @@ abstract class WidgetController { ); } - /// Creates a gesture with an initial down gesture at a particular point, and - /// returns the [TestGesture] object which you can use to continue the - /// gesture. + /// Creates a gesture with an initial appropriate starting gesture at a + /// particular point, and returns the [TestGesture] object which you can use + /// to continue the gesture. Usually, the starting gesture will be a down event, + /// but if [kind] is set to [PointerDeviceKind.trackpad], the gesture will start + /// with a panZoomStart gesture. /// /// You can use [createGesture] if your gesture doesn't begin with an initial - /// down gesture. + /// down or panZoomStart gesture. /// /// See also: /// * [WidgetController.drag], a method to simulate a drag. @@ -1110,7 +1112,11 @@ abstract class WidgetController { kind: kind, buttons: buttons, ); - await result.down(downLocation); + if (kind == PointerDeviceKind.trackpad) { + await result.panZoomStart(downLocation); + } else { + await result.down(downLocation); + } return result; } diff --git a/packages/flutter_test/lib/src/test_pointer.dart b/packages/flutter_test/lib/src/test_pointer.dart index 06e66050b668..a1789491e8bc 100644 --- a/packages/flutter_test/lib/src/test_pointer.dart +++ b/packages/flutter_test/lib/src/test_pointer.dart @@ -455,6 +455,7 @@ class TestGesture { /// Dispatch a pointer down event at the given `downLocation`, caching the /// hit test result. Future down(Offset downLocation, { Duration timeStamp = Duration.zero }) async { + assert(_pointer.kind != PointerDeviceKind.trackpad, 'Trackpads are expected to send panZoomStart events, not down events.'); return TestAsyncUtils.guard(() async { return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp)); }); @@ -463,6 +464,7 @@ class TestGesture { /// Dispatch a pointer down event at the given `downLocation`, caching the /// hit test result with a custom down event. Future downWithCustomEvent(Offset downLocation, PointerDownEvent event) async { + assert(_pointer.kind != PointerDeviceKind.trackpad, 'Trackpads are expected to send panZoomStart events, not down events'); _pointer.setDownInfo(event, downLocation); return TestAsyncUtils.guard(() async { return _dispatcher(event); @@ -507,7 +509,15 @@ class TestGesture { /// * [WidgetController.fling], a method to simulate a fling. Future moveBy(Offset offset, { Duration timeStamp = Duration.zero }) { assert(_pointer.location != null); - return moveTo(_pointer.location! + offset, timeStamp: timeStamp); + if (_pointer.isPanZoomActive) { + return panZoomUpdate( + _pointer.location!, + pan: (_pointer.pan ?? Offset.zero) + offset, + timeStamp: timeStamp + ); + } else { + return moveTo(_pointer.location! + offset, timeStamp: timeStamp); + } } /// Send a move event moving the pointer to the given location. @@ -521,6 +531,7 @@ class TestGesture { /// It sends move events at a given frequency and it is useful when there are listeners involved. /// * [WidgetController.fling], a method to simulate a fling. Future moveTo(Offset location, { Duration timeStamp = Duration.zero }) { + assert(_pointer.kind != PointerDeviceKind.trackpad); return TestAsyncUtils.guard(() { if (_pointer._isDown) { return _dispatcher(_pointer.move(location, timeStamp: timeStamp)); @@ -530,12 +541,19 @@ class TestGesture { }); } - /// End the gesture by releasing the pointer. + /// End the gesture by releasing the pointer. For trackpad pointers this + /// will send a panZoomEnd event instead of an up event. Future up({ Duration timeStamp = Duration.zero }) { return TestAsyncUtils.guard(() async { - assert(_pointer._isDown); - await _dispatcher(_pointer.up(timeStamp: timeStamp)); - assert(!_pointer._isDown); + if (_pointer.kind == PointerDeviceKind.trackpad) { + assert(_pointer._isPanZoomActive); + await _dispatcher(_pointer.panZoomEnd(timeStamp: timeStamp)); + assert(!_pointer._isPanZoomActive); + } else { + assert(_pointer._isDown); + await _dispatcher(_pointer.up(timeStamp: timeStamp)); + assert(!_pointer._isDown); + } }); } @@ -543,6 +561,7 @@ class TestGesture { /// system showed a modal dialog on top of the Flutter application, /// for instance). Future cancel({ Duration timeStamp = Duration.zero }) { + assert(_pointer.kind != PointerDeviceKind.trackpad, 'Trackpads do not send cancel events.'); return TestAsyncUtils.guard(() async { assert(_pointer._isDown); await _dispatcher(_pointer.cancel(timeStamp: timeStamp)); @@ -553,6 +572,7 @@ class TestGesture { /// Dispatch a pointer pan zoom start event at the given `location`, caching the /// hit test result. Future panZoomStart(Offset location, { Duration timeStamp = Duration.zero }) async { + assert(_pointer.kind == PointerDeviceKind.trackpad, 'Only trackpads can send PointerPanZoom events.'); return TestAsyncUtils.guard(() async { return _dispatcher(_pointer.panZoomStart(location, timeStamp: timeStamp)); }); @@ -566,6 +586,7 @@ class TestGesture { double rotation = 0, Duration timeStamp = Duration.zero }) async { + assert(_pointer.kind == PointerDeviceKind.trackpad, 'Only trackpads can send PointerPanZoom events.'); return TestAsyncUtils.guard(() async { return _dispatcher(_pointer.panZoomUpdate(location, pan: pan, @@ -580,6 +601,7 @@ class TestGesture { Future panZoomEnd({ Duration timeStamp = Duration.zero }) async { + assert(_pointer.kind == PointerDeviceKind.trackpad, 'Only trackpads can send PointerPanZoom events.'); return TestAsyncUtils.guard(() async { return _dispatcher(_pointer.panZoomEnd( timeStamp: timeStamp diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index c057a5099a2c..d4b532b14d7b 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -385,6 +385,40 @@ void main() { }, ); + testWidgets( + 'WidgetTester.drag works with trackpad kind', + (WidgetTester tester) async { + final List logs = []; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Listener( + onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'), + onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'), + onPointerUp: (PointerUpEvent event) => logs.add('up ${event.buttons}'), + onPointerPanZoomStart: (PointerPanZoomStartEvent event) => logs.add('panZoomStart'), + onPointerPanZoomUpdate: (PointerPanZoomUpdateEvent event) => logs.add('panZoomUpdate ${event.pan}'), + onPointerPanZoomEnd: (PointerPanZoomEndEvent event) => logs.add('panZoomEnd'), + child: const Text('test'), + ), + ), + ); + + await tester.drag(find.text('test'), const Offset(-150.0, 200.0), kind: PointerDeviceKind.trackpad); + + for(int i = 0; i < logs.length; i++) { + if (i == 0) { + expect(logs[i], 'panZoomStart'); + } else if (i != logs.length - 1) { + expect(logs[i], startsWith('panZoomUpdate')); + } else { + expect(logs[i], 'panZoomEnd'); + } + } + }, + ); + testWidgets( 'WidgetTester.fling must respect buttons', (WidgetTester tester) async {