| // 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/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| const Offset forcePressOffset = Offset(400.0, 50.0); |
| |
| testWidgets('Uncontested scrolls start immediately', (WidgetTester tester) async { |
| bool didStartDrag = false; |
| double? updatedDragDelta; |
| bool didEndDrag = false; |
| |
| final Widget widget = GestureDetector( |
| onVerticalDragStart: (DragStartDetails details) { |
| didStartDrag = true; |
| }, |
| onVerticalDragUpdate: (DragUpdateDetails details) { |
| updatedDragDelta = details.primaryDelta; |
| }, |
| onVerticalDragEnd: (DragEndDetails details) { |
| didEndDrag = true; |
| }, |
| child: Container( |
| color: const Color(0xFF00FF00), |
| ), |
| ); |
| |
| await tester.pumpWidget(widget); |
| expect(didStartDrag, isFalse); |
| expect(updatedDragDelta, isNull); |
| expect(didEndDrag, isFalse); |
| |
| const Offset firstLocation = Offset(10.0, 10.0); |
| final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); |
| expect(didStartDrag, isTrue); |
| didStartDrag = false; |
| expect(updatedDragDelta, isNull); |
| expect(didEndDrag, isFalse); |
| |
| const Offset secondLocation = Offset(10.0, 9.0); |
| await gesture.moveTo(secondLocation); |
| expect(didStartDrag, isFalse); |
| expect(updatedDragDelta, -1.0); |
| updatedDragDelta = null; |
| expect(didEndDrag, isFalse); |
| |
| await gesture.up(); |
| expect(didStartDrag, isFalse); |
| expect(updatedDragDelta, isNull); |
| expect(didEndDrag, isTrue); |
| didEndDrag = false; |
| |
| await tester.pumpWidget(Container()); |
| }); |
| |
| testWidgets('Match two scroll gestures in succession', (WidgetTester tester) async { |
| int gestureCount = 0; |
| double dragDistance = 0.0; |
| |
| const Offset downLocation = Offset(10.0, 10.0); |
| const Offset upLocation = Offset(10.0, 50.0); // must be far enough to be more than kTouchSlop |
| |
| final Widget widget = GestureDetector( |
| dragStartBehavior: DragStartBehavior.down, |
| onVerticalDragUpdate: (DragUpdateDetails details) { dragDistance += details.primaryDelta ?? 0; }, |
| onVerticalDragEnd: (DragEndDetails details) { gestureCount += 1; }, |
| onHorizontalDragUpdate: (DragUpdateDetails details) { fail('gesture should not match'); }, |
| onHorizontalDragEnd: (DragEndDetails details) { fail('gesture should not match'); }, |
| child: Container( |
| color: const Color(0xFF00FF00), |
| ), |
| ); |
| await tester.pumpWidget(widget); |
| |
| TestGesture gesture = await tester.startGesture(downLocation, pointer: 7); |
| await gesture.moveTo(upLocation); |
| await gesture.up(); |
| |
| gesture = await tester.startGesture(downLocation, pointer: 7); |
| await gesture.moveTo(upLocation); |
| await gesture.up(); |
| |
| expect(gestureCount, 2); |
| expect(dragDistance, 40.0 * 2.0); // delta between down and up, twice |
| |
| await tester.pumpWidget(Container()); |
| }); |
| |
| testWidgets("Pan doesn't crash", (WidgetTester tester) async { |
| bool didStartPan = false; |
| Offset? panDelta; |
| bool didEndPan = false; |
| |
| await tester.pumpWidget( |
| GestureDetector( |
| onPanStart: (DragStartDetails details) { |
| didStartPan = true; |
| }, |
| onPanUpdate: (DragUpdateDetails details) { |
| panDelta = (panDelta ?? Offset.zero) + details.delta; |
| }, |
| onPanEnd: (DragEndDetails details) { |
| didEndPan = true; |
| }, |
| child: Container( |
| color: const Color(0xFF00FF00), |
| ), |
| ), |
| ); |
| |
| expect(didStartPan, isFalse); |
| expect(panDelta, isNull); |
| expect(didEndPan, isFalse); |
| |
| await tester.dragFrom(const Offset(10.0, 10.0), const Offset(20.0, 30.0)); |
| |
| expect(didStartPan, isTrue); |
| expect(panDelta!.dx, 20.0); |
| expect(panDelta!.dy, 30.0); |
| expect(didEndPan, isTrue); |
| }); |
| |
| group('Tap', () { |
| final ButtonVariant buttonVariant = ButtonVariant( |
| values: <int>[kPrimaryButton, kSecondaryButton, kTertiaryButton], |
| descriptions: <int, String>{ |
| kPrimaryButton: 'primary', |
| kSecondaryButton: 'secondary', |
| kTertiaryButton: 'tertiary', |
| }, |
| ); |
| |
| testWidgets('Translucent', (WidgetTester tester) async { |
| bool didReceivePointerDown; |
| bool didTap; |
| |
| Future<void> pumpWidgetTree(HitTestBehavior? behavior) { |
| return tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Stack( |
| children: <Widget>[ |
| Listener( |
| onPointerDown: (_) { |
| didReceivePointerDown = true; |
| }, |
| child: Container( |
| width: 100.0, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| ), |
| ), |
| SizedBox( |
| width: 100.0, |
| height: 100.0, |
| child: GestureDetector( |
| onTap: ButtonVariant.button == kPrimaryButton ? () { |
| didTap = true; |
| } : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { |
| didTap = true; |
| } : null, |
| onTertiaryTapDown: ButtonVariant.button == kTertiaryButton ? (_) { |
| didTap = true; |
| } : null, |
| behavior: behavior, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| didReceivePointerDown = false; |
| didTap = false; |
| await pumpWidgetTree(null); |
| await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button); |
| expect(didReceivePointerDown, isTrue); |
| expect(didTap, isTrue); |
| |
| didReceivePointerDown = false; |
| didTap = false; |
| await pumpWidgetTree(HitTestBehavior.deferToChild); |
| await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button); |
| expect(didReceivePointerDown, isTrue); |
| expect(didTap, isFalse); |
| |
| didReceivePointerDown = false; |
| didTap = false; |
| await pumpWidgetTree(HitTestBehavior.opaque); |
| await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button); |
| expect(didReceivePointerDown, isFalse); |
| expect(didTap, isTrue); |
| |
| didReceivePointerDown = false; |
| didTap = false; |
| await pumpWidgetTree(HitTestBehavior.translucent); |
| await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button); |
| expect(didReceivePointerDown, isTrue); |
| expect(didTap, isTrue); |
| }, variant: buttonVariant); |
| |
| testWidgets('Empty', (WidgetTester tester) async { |
| bool didTap = false; |
| await tester.pumpWidget( |
| Center( |
| child: GestureDetector( |
| onTap: ButtonVariant.button == kPrimaryButton ? () { |
| didTap = true; |
| } : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { |
| didTap = true; |
| } : null, |
| onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) { |
| didTap = true; |
| } : null, |
| ), |
| ), |
| ); |
| expect(didTap, isFalse); |
| await tester.tapAt(const Offset(10.0, 10.0), buttons: ButtonVariant.button); |
| expect(didTap, isTrue); |
| }, variant: buttonVariant); |
| |
| testWidgets('Only container', (WidgetTester tester) async { |
| bool didTap = false; |
| await tester.pumpWidget( |
| Center( |
| child: GestureDetector( |
| onTap: ButtonVariant.button == kPrimaryButton ? () { |
| didTap = true; |
| } : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { |
| didTap = true; |
| } : null, |
| onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) { |
| didTap = true; |
| } : null, |
| child: Container(), |
| ), |
| ), |
| ); |
| expect(didTap, isFalse); |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| expect(didTap, isFalse); |
| }, variant: buttonVariant); |
| |
| testWidgets('cache render object', (WidgetTester tester) async { |
| void inputCallback() { } |
| |
| await tester.pumpWidget( |
| Center( |
| child: GestureDetector( |
| onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null, |
| onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) => inputCallback() : null, |
| child: Container(), |
| ), |
| ), |
| ); |
| |
| final RenderSemanticsGestureHandler renderObj1 = tester.renderObject(find.byType(GestureDetector)); |
| |
| await tester.pumpWidget( |
| Center( |
| child: GestureDetector( |
| onTap: ButtonVariant.button == kPrimaryButton ? inputCallback : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? inputCallback : null, |
| onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (_) => inputCallback() : null, |
| child: Container(), |
| ), |
| ), |
| ); |
| |
| final RenderSemanticsGestureHandler renderObj2 = tester.renderObject(find.byType(GestureDetector)); |
| |
| expect(renderObj1, same(renderObj2)); |
| }, variant: buttonVariant); |
| |
| testWidgets('Tap down occurs after kPressTimeout', (WidgetTester tester) async { |
| int tapDown = 0; |
| int tap = 0; |
| int tapCancel = 0; |
| int longPress = 0; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: RawGestureDetector( |
| behavior: HitTestBehavior.translucent, |
| // Adding long press callbacks here will cause the on*TapDown callbacks to be executed only after |
| // kPressTimeout has passed. Without the long press callbacks, there would be no press pointers |
| // competing in the arena. Hence, we add them to the arena to test this behavior. |
| // |
| // We use a raw gesture detector directly here because gesture detector does |
| // not expose callbacks for the tertiary variant of long presses, i.e. no onTertiaryLongPress* |
| // callbacks are exposed in GestureDetector. |
| // |
| // The primary and secondary long press callbacks could also be put into the gesture detector below, |
| // however, it is clearer when they are all in one place. |
| gestures: <Type, GestureRecognizerFactory>{ |
| LongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( |
| () => LongPressGestureRecognizer(), |
| (LongPressGestureRecognizer instance) { |
| instance |
| ..onLongPress = ButtonVariant.button == kPrimaryButton ? () { |
| longPress += 1; |
| } : null |
| ..onSecondaryLongPress = ButtonVariant.button == kSecondaryButton ? () { |
| longPress += 1; |
| } : null |
| ..onTertiaryLongPress = ButtonVariant.button == kTertiaryButton ? () { |
| longPress += 1; |
| } : null; |
| }, |
| ), |
| }, |
| child: GestureDetector( |
| onTapDown: ButtonVariant.button == kPrimaryButton ? (TapDownDetails details) { |
| tapDown += 1; |
| } : null, |
| onSecondaryTapDown: ButtonVariant.button == kSecondaryButton ? (TapDownDetails details) { |
| tapDown += 1; |
| } : null, |
| onTertiaryTapDown: ButtonVariant.button == kTertiaryButton ? (TapDownDetails details) { |
| tapDown += 1; |
| } : null, |
| onTap: ButtonVariant.button == kPrimaryButton ? () { |
| tap += 1; |
| } : null, |
| onSecondaryTap: ButtonVariant.button == kSecondaryButton ? () { |
| tap += 1; |
| } : null, |
| onTertiaryTapUp: ButtonVariant.button == kTertiaryButton ? (TapUpDetails details) { |
| tap += 1; |
| } : null, |
| onTapCancel: ButtonVariant.button == kPrimaryButton ? () { |
| tapCancel += 1; |
| } : null, |
| onSecondaryTapCancel: ButtonVariant.button == kSecondaryButton ? () { |
| tapCancel += 1; |
| } : null, |
| onTertiaryTapCancel: ButtonVariant.button == kTertiaryButton ? () { |
| tapCancel += 1; |
| } : null, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Pointer is dragged from the center of the 800x100 gesture detector |
| // to a point (400,300) below it. This should never call onTap. |
| Future<void> dragOut(Duration timeout) async { |
| final TestGesture gesture = |
| await tester.startGesture(const Offset(400.0, 50.0), buttons: ButtonVariant.button); |
| // If the timeout is less than kPressTimeout the recognizer will not |
| // trigger any callbacks. If the timeout is greater than kLongPressTimeout |
| // then onTapDown, onLongPress, and onCancel will be called. |
| await tester.pump(timeout); |
| await gesture.moveTo(const Offset(400.0, 300.0)); |
| await gesture.up(); |
| } |
| |
| await dragOut(kPressTimeout * 0.5); // generates nothing |
| expect(tapDown, 0); |
| expect(tapCancel, 0); |
| expect(tap, 0); |
| expect(longPress, 0); |
| |
| await dragOut(kPressTimeout); // generates tapDown, tapCancel |
| expect(tapDown, 1); |
| expect(tapCancel, 1); |
| expect(tap, 0); |
| expect(longPress, 0); |
| |
| await dragOut(kLongPressTimeout); // generates tapDown, longPress, tapCancel |
| expect(tapDown, 2); |
| expect(tapCancel, 2); |
| expect(tap, 0); |
| expect(longPress, 1); |
| }, variant: buttonVariant); |
| |
| testWidgets('Long Press Up Callback called after long press', (WidgetTester tester) async { |
| int longPressUp = 0; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: RawGestureDetector( |
| // We use a raw gesture detector directly here because gesture detector does |
| // not expose callbacks for the tertiary variant of long presses, i.e. no onTertiaryLongPress* |
| // callbacks are exposed in GestureDetector, and we want to test all three variants. |
| // |
| // The primary and secondary long press callbacks could also be put into the gesture detector below, |
| // however, it is more convenient to have them all in one place. |
| gestures: <Type, GestureRecognizerFactory>{ |
| LongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( |
| () => LongPressGestureRecognizer(), |
| (LongPressGestureRecognizer instance) { |
| instance |
| ..onLongPressUp = ButtonVariant.button == kPrimaryButton ? () { |
| longPressUp += 1; |
| } : null |
| ..onSecondaryLongPressUp = ButtonVariant.button == kSecondaryButton ? () { |
| longPressUp += 1; |
| } : null |
| ..onTertiaryLongPressUp = ButtonVariant.button == kTertiaryButton ? () { |
| longPressUp += 1; |
| } : null; |
| }, |
| ), |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| Future<void> longPress(Duration timeout) async { |
| final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0), buttons: ButtonVariant.button); |
| await tester.pump(timeout); |
| await gesture.up(); |
| } |
| |
| await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred |
| expect(longPressUp, 1); |
| }, variant: buttonVariant); |
| }); |
| |
| testWidgets('Primary and secondary long press callbacks should work together in GestureDetector', (WidgetTester tester) async { |
| bool primaryLongPress = false, secondaryLongPress = false; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: GestureDetector( |
| onLongPress: () { |
| primaryLongPress = true; |
| }, |
| onSecondaryLongPress: () { |
| secondaryLongPress = true; |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| Future<void> longPress(Duration timeout, int buttons) async { |
| final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0), buttons: buttons); |
| await tester.pump(timeout); |
| await gesture.up(); |
| } |
| |
| // Adding a second to make sure the time for long press has occurred. |
| await longPress(kLongPressTimeout + const Duration(seconds: 1), kPrimaryButton); |
| expect(primaryLongPress, isTrue); |
| |
| await longPress(kLongPressTimeout + const Duration(seconds: 1), kSecondaryButton); |
| expect(secondaryLongPress, isTrue); |
| }); |
| |
| testWidgets('Force Press Callback called after force press', (WidgetTester tester) async { |
| int forcePressStart = 0; |
| int forcePressPeaked = 0; |
| int forcePressUpdate = 0; |
| int forcePressEnded = 0; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: GestureDetector( |
| onForcePressStart: (_) => forcePressStart += 1, |
| onForcePressEnd: (_) => forcePressEnded += 1, |
| onForcePressPeak: (_) => forcePressPeaked += 1, |
| onForcePressUpdate: (_) => forcePressUpdate += 1, |
| ), |
| ), |
| ), |
| ); |
| final int pointerValue = tester.nextPointer; |
| |
| final TestGesture gesture = await tester.createGesture(); |
| await gesture.downWithCustomEvent( |
| forcePressOffset, |
| PointerDownEvent( |
| pointer: pointerValue, |
| position: forcePressOffset, |
| pressure: 0.0, |
| pressureMax: 6.0, |
| pressureMin: 0.0, |
| ), |
| ); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.3, |
| pressureMin: 0, |
| )); |
| |
| expect(forcePressStart, 0); |
| expect(forcePressPeaked, 0); |
| expect(forcePressUpdate, 0); |
| expect(forcePressEnded, 0); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.5, |
| pressureMin: 0, |
| )); |
| |
| expect(forcePressStart, 1); |
| expect(forcePressPeaked, 0); |
| expect(forcePressUpdate, 1); |
| expect(forcePressEnded, 0); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.6, |
| pressureMin: 0, |
| )); |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.7, |
| pressureMin: 0, |
| )); |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.2, |
| pressureMin: 0, |
| )); |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.3, |
| pressureMin: 0, |
| )); |
| |
| expect(forcePressStart, 1); |
| expect(forcePressPeaked, 0); |
| expect(forcePressUpdate, 5); |
| expect(forcePressEnded, 0); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.9, |
| pressureMin: 0, |
| )); |
| |
| expect(forcePressStart, 1); |
| expect(forcePressPeaked, 1); |
| expect(forcePressUpdate, 6); |
| expect(forcePressEnded, 0); |
| |
| await gesture.up(); |
| |
| expect(forcePressStart, 1); |
| expect(forcePressPeaked, 1); |
| expect(forcePressUpdate, 6); |
| expect(forcePressEnded, 1); |
| }); |
| |
| testWidgets('Force Press Callback not called if long press triggered before force press', (WidgetTester tester) async { |
| int forcePressStart = 0; |
| int longPressTimes = 0; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: GestureDetector( |
| onForcePressStart: (_) => forcePressStart += 1, |
| onLongPress: () => longPressTimes += 1, |
| ), |
| ), |
| ), |
| ); |
| |
| final int pointerValue = tester.nextPointer; |
| const double maxPressure = 6.0; |
| |
| final TestGesture gesture = await tester.createGesture(); |
| |
| await gesture.downWithCustomEvent( |
| forcePressOffset, |
| PointerDownEvent( |
| pointer: pointerValue, |
| position: forcePressOffset, |
| pressure: 0.0, |
| pressureMax: maxPressure, |
| pressureMin: 0.0, |
| ), |
| ); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| position: const Offset(400.0, 50.0), |
| pressure: 0.3, |
| pressureMin: 0, |
| pressureMax: maxPressure, |
| )); |
| |
| expect(forcePressStart, 0); |
| expect(longPressTimes, 0); |
| |
| // Trigger the long press. |
| await tester.pump(kLongPressTimeout + const Duration(seconds: 1)); |
| |
| expect(longPressTimes, 1); |
| expect(forcePressStart, 0); |
| |
| // Failed attempt to trigger the force press. |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| position: const Offset(400.0, 50.0), |
| pressure: 0.5, |
| pressureMin: 0, |
| pressureMax: maxPressure, |
| )); |
| |
| expect(longPressTimes, 1); |
| expect(forcePressStart, 0); |
| }); |
| |
| testWidgets('Force Press Callback not called if drag triggered before force press', (WidgetTester tester) async { |
| int forcePressStart = 0; |
| int horizontalDragStart = 0; |
| |
| await tester.pumpWidget( |
| Container( |
| alignment: Alignment.topLeft, |
| child: Container( |
| alignment: Alignment.center, |
| height: 100.0, |
| color: const Color(0xFF00FF00), |
| child: GestureDetector( |
| onForcePressStart: (_) => forcePressStart += 1, |
| onHorizontalDragStart: (_) => horizontalDragStart += 1, |
| ), |
| ), |
| ), |
| ); |
| |
| final int pointerValue = tester.nextPointer; |
| |
| final TestGesture gesture = await tester.createGesture(); |
| |
| await gesture.downWithCustomEvent( |
| forcePressOffset, |
| PointerDownEvent( |
| pointer: pointerValue, |
| position: forcePressOffset, |
| pressure: 0.0, |
| pressureMax: 6.0, |
| pressureMin: 0.0, |
| ), |
| ); |
| |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.3, |
| pressureMin: 0, |
| )); |
| |
| expect(forcePressStart, 0); |
| expect(horizontalDragStart, 0); |
| |
| // Trigger horizontal drag. |
| await gesture.moveBy(const Offset(100, 0)); |
| |
| expect(horizontalDragStart, 1); |
| expect(forcePressStart, 0); |
| |
| // Failed attempt to trigger the force press. |
| await gesture.updateWithCustomEvent(PointerMoveEvent( |
| pointer: pointerValue, |
| pressure: 0.5, |
| pressureMin: 0, |
| )); |
| |
| expect(horizontalDragStart, 1); |
| expect(forcePressStart, 0); |
| }); |
| |
| group("RawGestureDetectorState's debugFillProperties", () { |
| testWidgets('when default', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(RawGestureDetector( |
| key: key, |
| )); |
| key.currentState!.debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
| .map((DiagnosticsNode node) => node.toString()) |
| .toList(); |
| |
| expect(description, <String>[ |
| 'gestures: <none>', |
| ]); |
| }); |
| |
| testWidgets('should show gestures, custom semantics and behavior', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(RawGestureDetector( |
| key: key, |
| behavior: HitTestBehavior.deferToChild, |
| gestures: <Type, GestureRecognizerFactory>{ |
| TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( |
| () => TapGestureRecognizer(), |
| (TapGestureRecognizer recognizer) { |
| recognizer.onTap = () {}; |
| }, |
| ), |
| LongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( |
| () => LongPressGestureRecognizer(), |
| (LongPressGestureRecognizer recognizer) { |
| recognizer.onLongPress = () {}; |
| }, |
| ), |
| }, |
| semantics: _EmptySemanticsGestureDelegate(), |
| child: Container(), |
| )); |
| key.currentState!.debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
| .map((DiagnosticsNode node) => node.toString()) |
| .toList(); |
| |
| expect(description, <String>[ |
| 'gestures: tap, long press', |
| 'semantics: _EmptySemanticsGestureDelegate()', |
| 'behavior: deferToChild', |
| ]); |
| }); |
| |
| testWidgets('should not show semantics when excludeFromSemantics is true', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(RawGestureDetector( |
| key: key, |
| semantics: _EmptySemanticsGestureDelegate(), |
| excludeFromSemantics: true, |
| child: Container(), |
| )); |
| key.currentState!.debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
| .map((DiagnosticsNode node) => node.toString()) |
| .toList(); |
| |
| expect(description, <String>[ |
| 'gestures: <none>', |
| 'excludeFromSemantics: true', |
| ]); |
| }); |
| |
| group('error control test', () { |
| test('constructor redundant pan and scale', () { |
| late FlutterError error; |
| try { |
| GestureDetector(onScaleStart: (_) {}, onPanStart: (_) {}); |
| } on FlutterError catch (e) { |
| error = e; |
| } finally { |
| expect( |
| error.toStringDeep(), |
| 'FlutterError\n' |
| ' Incorrect GestureDetector arguments.\n' |
| ' Having both a pan gesture recognizer and a scale gesture\n' |
| ' recognizer is redundant; scale is a superset of pan.\n' |
| ' Just use the scale gesture recognizer.\n', |
| ); |
| expect(error.diagnostics.last.level, DiagnosticLevel.hint); |
| expect( |
| error.diagnostics.last.toStringDeep(), |
| equalsIgnoringHashCodes( |
| 'Just use the scale gesture recognizer.\n', |
| ), |
| ); |
| } |
| }); |
| |
| test('constructor duplicate drag recognizer', () { |
| late FlutterError error; |
| try { |
| GestureDetector( |
| onVerticalDragStart: (_) {}, |
| onHorizontalDragStart: (_) {}, |
| onPanStart: (_) {}, |
| ); |
| } on FlutterError catch (e) { |
| error = e; |
| } finally { |
| expect( |
| error.toStringDeep(), |
| 'FlutterError\n' |
| ' Incorrect GestureDetector arguments.\n' |
| ' Simultaneously having a vertical drag gesture recognizer, a\n' |
| ' horizontal drag gesture recognizer, and a pan gesture recognizer\n' |
| ' will result in the pan gesture recognizer being ignored, since\n' |
| ' the other two will catch all drags.\n', |
| ); |
| } |
| }); |
| |
| testWidgets('replaceGestureRecognizers not during layout', (WidgetTester tester) async { |
| final GlobalKey<RawGestureDetectorState> key = GlobalKey<RawGestureDetectorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: RawGestureDetector( |
| key: key, |
| child: const Text('Text'), |
| ), |
| ), |
| ); |
| late FlutterError error; |
| try { |
| key.currentState!.replaceGestureRecognizers(<Type, GestureRecognizerFactory>{}); |
| } on FlutterError catch (e) { |
| error = e; |
| } finally { |
| expect(error.diagnostics.last.level, DiagnosticLevel.hint); |
| expect( |
| error.diagnostics.last.toStringDeep(), |
| equalsIgnoringHashCodes( |
| 'To set the gesture recognizers at other times, trigger a new\n' |
| 'build using setState() and provide the new gesture recognizers as\n' |
| 'constructor arguments to the corresponding RawGestureDetector or\n' |
| 'GestureDetector object.\n', |
| ), |
| ); |
| expect( |
| error.toStringDeep(), |
| 'FlutterError\n' |
| ' Unexpected call to replaceGestureRecognizers() method of\n' |
| ' RawGestureDetectorState.\n' |
| ' The replaceGestureRecognizers() method can only be called during\n' |
| ' the layout phase.\n' |
| ' To set the gesture recognizers at other times, trigger a new\n' |
| ' build using setState() and provide the new gesture recognizers as\n' |
| ' constructor arguments to the corresponding RawGestureDetector or\n' |
| ' GestureDetector object.\n', |
| ); |
| } |
| }); |
| }); |
| }); |
| } |
| |
| class _EmptySemanticsGestureDelegate extends SemanticsGestureDelegate { |
| @override |
| void assignSemantics(RenderSemanticsGestureHandler renderObject) { |
| } |
| } |
| |
| /// A [TestVariant] that runs tests multiple times with different buttons. |
| class ButtonVariant extends TestVariant<int> { |
| const ButtonVariant({ |
| required this.values, |
| required this.descriptions, |
| }) : assert(values.length != 0); |
| |
| @override |
| final List<int> values; |
| |
| final Map<int, String> descriptions; |
| |
| static int button = 0; |
| |
| @override |
| String describeValue(int value) { |
| assert(descriptions.containsKey(value), 'Unknown button'); |
| return descriptions[value]!; |
| } |
| |
| @override |
| Future<int> setUp(int value) async { |
| final int oldValue = button; |
| button = value; |
| return oldValue; |
| } |
| |
| @override |
| Future<void> tearDown(int value, int memento) async { |
| button = memento; |
| } |
| } |