| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'gesture_tester.dart'; |
| |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| |
| test('acceptGesture tolerates a null lastPendingEventTimestamp', () { |
| // Regression test for https://github.com/flutter/flutter/issues/112403 |
| // and b/249091367 |
| final DragGestureRecognizer recognizer = VerticalDragGestureRecognizer(); |
| const PointerDownEvent event = PointerDownEvent(timeStamp: Duration(days: 10)); |
| |
| expect(recognizer.debugLastPendingEventTimestamp, null); |
| |
| recognizer.addAllowedPointer(event); |
| expect(recognizer.debugLastPendingEventTimestamp, event.timeStamp); |
| |
| // Normal case: acceptGesture called and we have a last timestamp set. |
| recognizer.acceptGesture(event.pointer); |
| expect(recognizer.debugLastPendingEventTimestamp, null); |
| |
| // Reject the gesture to reset state and allow accepting it again. |
| recognizer.rejectGesture(event.pointer); |
| expect(recognizer.debugLastPendingEventTimestamp, null); |
| |
| // Not entirely clear how this can happen, but the bugs mentioned above show |
| // we can end up in this state empirically. |
| recognizer.acceptGesture(event.pointer); |
| expect(recognizer.debugLastPendingEventTimestamp, null); |
| }); |
| |
| testGesture('do not crash on up event for a pending pointer after winning arena for another pointer', (GestureTester tester) { |
| // Regression test for https://github.com/flutter/flutter/issues/75061. |
| |
| final VerticalDragGestureRecognizer v = VerticalDragGestureRecognizer() |
| ..onStart = (_) { }; |
| addTearDown(v.dispose); |
| final HorizontalDragGestureRecognizer h = HorizontalDragGestureRecognizer() |
| ..onStart = (_) { }; |
| addTearDown(h.dispose); |
| |
| const PointerDownEvent down90 = PointerDownEvent( |
| pointer: 90, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| const PointerUpEvent up90 = PointerUpEvent( |
| pointer: 90, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| const PointerDownEvent down91 = PointerDownEvent( |
| pointer: 91, |
| position: Offset(20.0, 20.0), |
| ); |
| |
| const PointerUpEvent up91 = PointerUpEvent( |
| pointer: 91, |
| position: Offset(20.0, 20.0), |
| ); |
| |
| v.addPointer(down90); |
| GestureBinding.instance.gestureArena.close(90); |
| h.addPointer(down91); |
| v.addPointer(down91); |
| GestureBinding.instance.gestureArena.close(91); |
| tester.async.flushMicrotasks(); |
| |
| GestureBinding.instance.handleEvent(up90, HitTestEntry(MockHitTestTarget())); |
| GestureBinding.instance.handleEvent(up91, HitTestEntry(MockHitTestTarget())); |
| }); |
| |
| testGesture('DragGestureRecognizer should not dispatch drag callbacks when it wins the arena if onlyAcceptDragOnThreshold is true and the threshold has not been met', (GestureTester tester) { |
| final VerticalDragGestureRecognizer verticalDrag = VerticalDragGestureRecognizer(); |
| final List<String> dragCallbacks = <String>[]; |
| verticalDrag |
| ..onlyAcceptDragOnThreshold = true |
| ..onStart = (DragStartDetails details) { |
| dragCallbacks.add('onStart'); |
| } |
| ..onUpdate = (DragUpdateDetails details) { |
| dragCallbacks.add('onUpdate'); |
| } |
| ..onEnd = (DragEndDetails details) { |
| dragCallbacks.add('onEnd'); |
| }; |
| |
| const PointerDownEvent down1 = PointerDownEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| const PointerUpEvent up1 = PointerUpEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| verticalDrag.addPointer(down1); |
| tester.closeArena(down1.pointer); |
| tester.route(down1); |
| tester.route(up1); |
| expect(dragCallbacks.isEmpty, true); |
| verticalDrag.dispose(); |
| dragCallbacks.clear(); |
| }); |
| |
| testGesture('DragGestureRecognizer should dispatch drag callbacks when it wins the arena if onlyAcceptDragOnThreshold is false and the threshold has not been met', (GestureTester tester) { |
| final VerticalDragGestureRecognizer verticalDrag = VerticalDragGestureRecognizer(); |
| final List<String> dragCallbacks = <String>[]; |
| verticalDrag |
| ..onlyAcceptDragOnThreshold = false |
| ..onStart = (DragStartDetails details) { |
| dragCallbacks.add('onStart'); |
| } |
| ..onUpdate = (DragUpdateDetails details) { |
| dragCallbacks.add('onUpdate'); |
| } |
| ..onEnd = (DragEndDetails details) { |
| dragCallbacks.add('onEnd'); |
| }; |
| |
| const PointerDownEvent down1 = PointerDownEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| const PointerUpEvent up1 = PointerUpEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| verticalDrag.addPointer(down1); |
| tester.closeArena(down1.pointer); |
| tester.route(down1); |
| tester.route(up1); |
| expect(dragCallbacks.isEmpty, false); |
| expect(dragCallbacks, <String>['onStart', 'onEnd']); |
| verticalDrag.dispose(); |
| dragCallbacks.clear(); |
| }); |
| |
| group('Recognizers on different button filters:', () { |
| final List<String> recognized = <String>[]; |
| late HorizontalDragGestureRecognizer primaryRecognizer; |
| late HorizontalDragGestureRecognizer secondaryRecognizer; |
| setUp(() { |
| primaryRecognizer = HorizontalDragGestureRecognizer( |
| allowedButtonsFilter: (int buttons) => kPrimaryButton == buttons) |
| ..onStart = (DragStartDetails details) { |
| recognized.add('onStartPrimary'); |
| }; |
| secondaryRecognizer = HorizontalDragGestureRecognizer( |
| allowedButtonsFilter: (int buttons) => kSecondaryButton == buttons) |
| ..onStart = (DragStartDetails details) { |
| recognized.add('onStartSecondary'); |
| }; |
| }); |
| |
| tearDown(() { |
| recognized.clear(); |
| primaryRecognizer.dispose(); |
| secondaryRecognizer.dispose(); |
| }); |
| |
| testGesture('Primary button works', (GestureTester tester) { |
| const PointerDownEvent down1 = PointerDownEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| ); |
| |
| primaryRecognizer.addPointer(down1); |
| secondaryRecognizer.addPointer(down1); |
| tester.closeArena(down1.pointer); |
| tester.route(down1); |
| expect(recognized, <String>['onStartPrimary']); |
| }); |
| |
| testGesture('Secondary button works', (GestureTester tester) { |
| const PointerDownEvent down1 = PointerDownEvent( |
| pointer: 6, |
| position: Offset(10.0, 10.0), |
| buttons: kSecondaryMouseButton, |
| ); |
| |
| primaryRecognizer.addPointer(down1); |
| secondaryRecognizer.addPointer(down1); |
| tester.closeArena(down1.pointer); |
| tester.route(down1); |
| expect(recognized, <String>['onStartSecondary']); |
| }); |
| }); |
| } |
| |
| class MockHitTestTarget implements HitTestTarget { |
| @override |
| void handleEvent(PointerEvent event, HitTestEntry entry) { } |
| } |