938 lines
31 KiB
Dart
938 lines
31 KiB
Dart
// 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_test/flutter_test.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/gestures.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,
|
|
position: Offset.zero,
|
|
pressure: 0.3,
|
|
pressureMin: 0,
|
|
pressureMax: 1,
|
|
));
|
|
|
|
expect(forcePressStart, 0);
|
|
expect(forcePressPeaked, 0);
|
|
expect(forcePressUpdate, 0);
|
|
expect(forcePressEnded, 0);
|
|
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.5,
|
|
pressureMin: 0,
|
|
pressureMax: 1
|
|
));
|
|
|
|
expect(forcePressStart, 1);
|
|
expect(forcePressPeaked, 0);
|
|
expect(forcePressUpdate, 1);
|
|
expect(forcePressEnded, 0);
|
|
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.6,
|
|
pressureMin: 0,
|
|
pressureMax: 1,
|
|
));
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.7,
|
|
pressureMin: 0,
|
|
pressureMax: 1
|
|
));
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.2,
|
|
pressureMin: 0,
|
|
pressureMax: 1
|
|
));
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.3,
|
|
pressureMin: 0,
|
|
pressureMax: 1
|
|
));
|
|
|
|
expect(forcePressStart, 1);
|
|
expect(forcePressPeaked, 0);
|
|
expect(forcePressUpdate, 5);
|
|
expect(forcePressEnded, 0);
|
|
|
|
await gesture.updateWithCustomEvent(PointerMoveEvent(
|
|
pointer: pointerValue,
|
|
position: Offset.zero,
|
|
pressure: 0.9,
|
|
pressureMin: 0,
|
|
pressureMax: 1,
|
|
));
|
|
|
|
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,
|
|
position: Offset.zero,
|
|
pressure: 0.3,
|
|
pressureMin: 0,
|
|
pressureMax: 1
|
|
));
|
|
|
|
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,
|
|
position: Offset.zero,
|
|
pressure: 0.5,
|
|
pressureMin: 0,
|
|
pressureMax: 1,
|
|
));
|
|
|
|
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 = () {};
|
|
},
|
|
),
|
|
},
|
|
child: Container(),
|
|
semantics: _EmptySemanticsGestureDelegate(),
|
|
));
|
|
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,
|
|
gestures: const <Type, GestureRecognizerFactory>{},
|
|
child: Container(),
|
|
semantics: _EmptySemanticsGestureDelegate(),
|
|
excludeFromSemantics: true,
|
|
));
|
|
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); // ignore: prefer_is_empty
|
|
|
|
@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;
|
|
}
|
|
}
|