Unverified Commit 20299a2c authored by Tong Mu's avatar Tong Mu Committed by GitHub

Add buttons customization to WidgetController and related testing classes (#31095)

* Add buttons to WidgetController and TestPointer

* Add more buttons

* Let TestPointer handle default device

* Use getter only buttons
parent f545f47d
...@@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; ...@@ -15,6 +15,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart' show kPrimaryButton;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/gallery/demos.dart'; import 'package:flutter_gallery/gallery/demos.dart';
...@@ -132,8 +133,8 @@ class _LiveWidgetController extends LiveWidgetController { ...@@ -132,8 +133,8 @@ class _LiveWidgetController extends LiveWidgetController {
} }
@override @override
Future<void> tap(Finder finder, { int pointer }) async { Future<void> tap(Finder finder, { int pointer, int buttons = kPrimaryButton }) async {
await super.tap(await _waitForElement(finder), pointer: pointer); await super.tap(await _waitForElement(finder), pointer: pointer, buttons: buttons);
} }
Future<void> scrollIntoView(Finder finder, {double alignment}) async { Future<void> scrollIntoView(Finder finder, {double alignment}) async {
......
...@@ -252,14 +252,14 @@ abstract class WidgetController { ...@@ -252,14 +252,14 @@ abstract class WidgetController {
/// ///
/// If the center of the widget is not exposed, this might send events to /// If the center of the widget is not exposed, this might send events to
/// another object. /// another object.
Future<void> tap(Finder finder, {int pointer}) { Future<void> tap(Finder finder, {int pointer, int buttons = kPrimaryButton}) {
return tapAt(getCenter(finder), pointer: pointer); return tapAt(getCenter(finder), pointer: pointer, buttons: buttons);
} }
/// Dispatch a pointer down / pointer up sequence at the given location. /// Dispatch a pointer down / pointer up sequence at the given location.
Future<void> tapAt(Offset location, {int pointer}) { Future<void> tapAt(Offset location, {int pointer, int buttons = kPrimaryButton}) {
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
final TestGesture gesture = await startGesture(location, pointer: pointer); final TestGesture gesture = await startGesture(location, pointer: pointer, buttons: buttons);
await gesture.up(); await gesture.up();
}); });
} }
...@@ -269,9 +269,9 @@ abstract class WidgetController { ...@@ -269,9 +269,9 @@ abstract class WidgetController {
/// ///
/// If the center of the widget is not exposed, this might send events to /// If the center of the widget is not exposed, this might send events to
/// another object. /// another object.
Future<TestGesture> press(Finder finder, {int pointer}) { Future<TestGesture> press(Finder finder, {int pointer, int buttons = kPrimaryButton}) {
return TestAsyncUtils.guard<TestGesture>(() { return TestAsyncUtils.guard<TestGesture>(() {
return startGesture(getCenter(finder), pointer: pointer); return startGesture(getCenter(finder), pointer: pointer, buttons: buttons);
}); });
} }
...@@ -281,15 +281,15 @@ abstract class WidgetController { ...@@ -281,15 +281,15 @@ abstract class WidgetController {
/// ///
/// If the center of the widget is not exposed, this might send events to /// If the center of the widget is not exposed, this might send events to
/// another object. /// another object.
Future<void> longPress(Finder finder, {int pointer}) { Future<void> longPress(Finder finder, {int pointer, int buttons = kPrimaryButton}) {
return longPressAt(getCenter(finder), pointer: pointer); return longPressAt(getCenter(finder), pointer: pointer, buttons: buttons);
} }
/// Dispatch a pointer down / pointer up sequence at the given location with /// Dispatch a pointer down / pointer up sequence at the given location with
/// a delay of [kLongPressTimeout] + [kPressTimeout] between the two events. /// a delay of [kLongPressTimeout] + [kPressTimeout] between the two events.
Future<void> longPressAt(Offset location, {int pointer}) { Future<void> longPressAt(Offset location, {int pointer, int buttons = kPrimaryButton}) {
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
final TestGesture gesture = await startGesture(location, pointer: pointer); final TestGesture gesture = await startGesture(location, pointer: pointer, buttons: buttons);
await pump(kLongPressTimeout + kPressTimeout); await pump(kLongPressTimeout + kPressTimeout);
await gesture.up(); await gesture.up();
}); });
...@@ -320,6 +320,7 @@ abstract class WidgetController { ...@@ -320,6 +320,7 @@ abstract class WidgetController {
Offset offset, Offset offset,
double speed, { double speed, {
int pointer, int pointer,
int buttons = kPrimaryButton,
Duration frameInterval = const Duration(milliseconds: 16), Duration frameInterval = const Duration(milliseconds: 16),
Offset initialOffset = Offset.zero, Offset initialOffset = Offset.zero,
Duration initialOffsetDelay = const Duration(seconds: 1), Duration initialOffsetDelay = const Duration(seconds: 1),
...@@ -329,6 +330,7 @@ abstract class WidgetController { ...@@ -329,6 +330,7 @@ abstract class WidgetController {
offset, offset,
speed, speed,
pointer: pointer, pointer: pointer,
buttons: buttons,
frameInterval: frameInterval, frameInterval: frameInterval,
initialOffset: initialOffset, initialOffset: initialOffset,
initialOffsetDelay: initialOffsetDelay, initialOffsetDelay: initialOffsetDelay,
...@@ -365,6 +367,7 @@ abstract class WidgetController { ...@@ -365,6 +367,7 @@ abstract class WidgetController {
Offset offset, Offset offset,
double speed, { double speed, {
int pointer, int pointer,
int buttons = kPrimaryButton,
Duration frameInterval = const Duration(milliseconds: 16), Duration frameInterval = const Duration(milliseconds: 16),
Offset initialOffset = Offset.zero, Offset initialOffset = Offset.zero,
Duration initialOffsetDelay = const Duration(seconds: 1), Duration initialOffsetDelay = const Duration(seconds: 1),
...@@ -372,7 +375,7 @@ abstract class WidgetController { ...@@ -372,7 +375,7 @@ abstract class WidgetController {
assert(offset.distance > 0.0); assert(offset.distance > 0.0);
assert(speed > 0.0); // speed is pixels/second assert(speed > 0.0); // speed is pixels/second
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
final TestPointer testPointer = TestPointer(pointer ?? _getNextPointer()); final TestPointer testPointer = TestPointer(pointer ?? _getNextPointer(), PointerDeviceKind.touch, null, buttons);
final HitTestResult result = hitTestOnBinding(startLocation); final HitTestResult result = hitTestOnBinding(startLocation);
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * speed); final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * speed);
...@@ -434,9 +437,23 @@ abstract class WidgetController { ...@@ -434,9 +437,23 @@ abstract class WidgetController {
/// 'touchSlopY' variables should be set to 0. However, generally, these values /// 'touchSlopY' variables should be set to 0. However, generally, these values
/// should be left to their default values. /// should be left to their default values.
/// {@end template} /// {@end template}
Future<void> drag(Finder finder, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) { Future<void> drag(
Finder finder,
Offset offset, {
int pointer,
int buttons = kPrimaryButton,
double touchSlopX = kDragSlopDefault,
double touchSlopY = kDragSlopDefault,
}) {
assert(kDragSlopDefault > kTouchSlop); assert(kDragSlopDefault > kTouchSlop);
return dragFrom(getCenter(finder), offset, pointer: pointer, touchSlopX: touchSlopX, touchSlopY: touchSlopY); return dragFrom(
getCenter(finder),
offset,
pointer: pointer,
buttons: buttons,
touchSlopX: touchSlopX,
touchSlopY: touchSlopY,
);
} }
/// Attempts a drag gesture consisting of a pointer down, a move by /// Attempts a drag gesture consisting of a pointer down, a move by
...@@ -447,10 +464,17 @@ abstract class WidgetController { ...@@ -447,10 +464,17 @@ abstract class WidgetController {
/// instead. /// instead.
/// ///
/// {@macro flutter.flutter_test.drag} /// {@macro flutter.flutter_test.drag}
Future<void> dragFrom(Offset startLocation, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) { Future<void> dragFrom(
Offset startLocation,
Offset offset, {
int pointer,
int buttons = kPrimaryButton,
double touchSlopX = kDragSlopDefault,
double touchSlopY = kDragSlopDefault,
}) {
assert(kDragSlopDefault > kTouchSlop); assert(kDragSlopDefault > kTouchSlop);
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
final TestGesture gesture = await startGesture(startLocation, pointer: pointer); final TestGesture gesture = await startGesture(startLocation, pointer: pointer, buttons: buttons);
assert(gesture != null); assert(gesture != null);
final double xSign = offset.dx.sign; final double xSign = offset.dx.sign;
...@@ -538,14 +562,18 @@ abstract class WidgetController { ...@@ -538,14 +562,18 @@ abstract class WidgetController {
/// ///
/// You can use [startGesture] instead if your gesture begins with a down /// You can use [startGesture] instead if your gesture begins with a down
/// event. /// event.
Future<TestGesture> createGesture({int pointer, PointerDeviceKind kind = PointerDeviceKind.touch}) async { Future<TestGesture> createGesture({
final TestGesture gesture = TestGesture( int pointer,
PointerDeviceKind kind = PointerDeviceKind.touch,
int buttons = kPrimaryButton,
}) async {
return TestGesture(
hitTester: hitTestOnBinding, hitTester: hitTestOnBinding,
dispatcher: sendEventToBinding, dispatcher: sendEventToBinding,
kind: kind, kind: kind,
pointer: pointer ?? _getNextPointer(), pointer: pointer ?? _getNextPointer(),
buttons: buttons,
); );
return gesture;
} }
/// Creates a gesture with an initial down gesture at a particular point, and /// Creates a gesture with an initial down gesture at a particular point, and
...@@ -558,8 +586,13 @@ abstract class WidgetController { ...@@ -558,8 +586,13 @@ abstract class WidgetController {
Offset downLocation, { Offset downLocation, {
int pointer, int pointer,
PointerDeviceKind kind = PointerDeviceKind.touch, PointerDeviceKind kind = PointerDeviceKind.touch,
int buttons = kPrimaryButton,
}) async { }) async {
final TestGesture result = await createGesture(pointer: pointer, kind: kind); final TestGesture result = await createGesture(
pointer: pointer,
kind: kind,
buttons: buttons,
);
await result.down(downLocation); await result.down(downLocation);
return result; return result;
} }
......
...@@ -26,8 +26,12 @@ class TestPointer { ...@@ -26,8 +26,12 @@ class TestPointer {
this.pointer = 1, this.pointer = 1,
this.kind = PointerDeviceKind.touch, this.kind = PointerDeviceKind.touch,
this._device, this._device,
]) : assert(kind != null), int buttons = kPrimaryButton,
assert(pointer != null) { ])
: assert(kind != null),
assert(pointer != null),
assert(buttons != null),
_buttons = buttons {
switch (kind) { switch (kind) {
case PointerDeviceKind.mouse: case PointerDeviceKind.mouse:
_device ??= 1; _device ??= 1;
...@@ -57,6 +61,11 @@ class TestPointer { ...@@ -57,6 +61,11 @@ class TestPointer {
/// [PointerDeviceKind.touch]. /// [PointerDeviceKind.touch].
final PointerDeviceKind kind; final PointerDeviceKind kind;
/// The kind of buttons to simulate on Down and Move events. Defaults to
/// [kPrimaryButton].
int get buttons => _buttons;
int _buttons;
/// Whether the pointer simulated by this object is currently down. /// Whether the pointer simulated by this object is currently down.
/// ///
/// A pointer is released (goes up) by calling [up] or [cancel]. /// A pointer is released (goes up) by calling [up] or [cancel].
...@@ -73,8 +82,14 @@ class TestPointer { ...@@ -73,8 +82,14 @@ class TestPointer {
/// If a custom event is created outside of this class, this function is used /// If a custom event is created outside of this class, this function is used
/// to set the [isDown]. /// to set the [isDown].
bool setDownInfo(PointerEvent event, Offset newLocation) { bool setDownInfo(
PointerEvent event,
Offset newLocation, {
int buttons,
}) {
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
switch (event.runtimeType) { switch (event.runtimeType) {
case PointerDownEvent: case PointerDownEvent:
assert(!isDown); assert(!isDown);
...@@ -95,16 +110,26 @@ class TestPointer { ...@@ -95,16 +110,26 @@ class TestPointer {
/// ///
/// By default, the time stamp on the event is [Duration.zero]. You can give a /// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument. /// specific time stamp by passing the `timeStamp` argument.
PointerDownEvent down(Offset newLocation, { Duration timeStamp = Duration.zero }) { ///
/// By default, the set of buttons in the last down or move event is used.
/// You can give a specific set of buttons by passing the `buttons` argument.
PointerDownEvent down(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int buttons,
}) {
assert(!isDown); assert(!isDown);
_isDown = true; _isDown = true;
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
return PointerDownEvent( return PointerDownEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
kind: kind, kind: kind,
device: _device, device: _device,
pointer: pointer, pointer: pointer,
position: location, position: location,
buttons: _buttons,
); );
} }
...@@ -115,7 +140,14 @@ class TestPointer { ...@@ -115,7 +140,14 @@ class TestPointer {
/// ///
/// [isDown] must be true when this is called, since move events can only /// [isDown] must be true when this is called, since move events can only
/// be generated when the pointer is down. /// be generated when the pointer is down.
PointerMoveEvent move(Offset newLocation, { Duration timeStamp = Duration.zero }) { ///
/// By default, the set of buttons in the last down or move event is used.
/// You can give a specific set of buttons by passing the `buttons` argument.
PointerMoveEvent move(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int buttons,
}) {
assert( assert(
isDown, isDown,
'Move events can only be generated when the pointer is down. To ' 'Move events can only be generated when the pointer is down. To '
...@@ -123,6 +155,8 @@ class TestPointer { ...@@ -123,6 +155,8 @@ class TestPointer {
'up, use hover() instead.'); 'up, use hover() instead.');
final Offset delta = newLocation - location; final Offset delta = newLocation - location;
_location = newLocation; _location = newLocation;
if (buttons != null)
_buttons = buttons;
return PointerMoveEvent( return PointerMoveEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
kind: kind, kind: kind,
...@@ -130,6 +164,7 @@ class TestPointer { ...@@ -130,6 +164,7 @@ class TestPointer {
pointer: pointer, pointer: pointer,
position: newLocation, position: newLocation,
delta: delta, delta: delta,
buttons: _buttons,
); );
} }
...@@ -291,13 +326,16 @@ class TestGesture { ...@@ -291,13 +326,16 @@ class TestGesture {
@required HitTester hitTester, @required HitTester hitTester,
int pointer = 1, int pointer = 1,
PointerDeviceKind kind = PointerDeviceKind.touch, PointerDeviceKind kind = PointerDeviceKind.touch,
int device,
int buttons = kPrimaryButton,
}) : assert(dispatcher != null), }) : assert(dispatcher != null),
assert(hitTester != null), assert(hitTester != null),
assert(pointer != null), assert(pointer != null),
assert(kind != null), assert(kind != null),
assert(buttons != null),
_dispatcher = dispatcher, _dispatcher = dispatcher,
_hitTester = hitTester, _hitTester = hitTester,
_pointer = TestPointer(pointer, kind), _pointer = TestPointer(pointer, kind, device, buttons),
_result = null; _result = null;
/// Dispatch a pointer down event at the given `downLocation`, caching the /// Dispatch a pointer down event at the given `downLocation`, caching the
......
...@@ -264,4 +264,154 @@ void main() { ...@@ -264,4 +264,154 @@ void main() {
} }
}, },
); );
testWidgets(
'WidgetTester.tap must respect buttons',
(WidgetTester tester) async {
final List<String> logs = <String>[];
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}'),
child: const Text('test'),
),
),
);
await tester.tap(find.text('test'), buttons: kSecondaryMouseButton);
const String b = '$kSecondaryMouseButton';
for(int i = 0; i < logs.length; i++) {
if (i == 0)
expect(logs[i], 'down $b');
else if (i != logs.length - 1)
expect(logs[i], 'move $b');
else
expect(logs[i], 'up 0');
}
},
);
testWidgets(
'WidgetTester.press must respect buttons',
(WidgetTester tester) async {
final List<String> logs = <String>[];
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}'),
child: const Text('test'),
),
),
);
await tester.press(find.text('test'), buttons: kSecondaryMouseButton);
const String b = '$kSecondaryMouseButton';
expect(logs, equals(<String>['down $b']));
},
);
testWidgets(
'WidgetTester.longPress must respect buttons',
(WidgetTester tester) async {
final List<String> logs = <String>[];
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}'),
child: const Text('test'),
),
),
);
await tester.longPress(find.text('test'), buttons: kSecondaryMouseButton);
await tester.pumpAndSettle();
const String b = '$kSecondaryMouseButton';
for(int i = 0; i < logs.length; i++) {
if (i == 0)
expect(logs[i], 'down $b');
else if (i != logs.length - 1)
expect(logs[i], 'move $b');
else
expect(logs[i], 'up 0');
}
},
);
testWidgets(
'WidgetTester.drag must respect buttons',
(WidgetTester tester) async {
final List<String> logs = <String>[];
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}'),
child: const Text('test'),
),
),
);
await tester.drag(find.text('test'), const Offset(-150.0, 200.0), buttons: kSecondaryMouseButton);
const String b = '$kSecondaryMouseButton';
for(int i = 0; i < logs.length; i++) {
if (i == 0)
expect(logs[i], 'down $b');
else if (i != logs.length - 1)
expect(logs[i], 'move $b');
else
expect(logs[i], 'up 0');
}
},
);
testWidgets(
'WidgetTester.fling must respect buttons',
(WidgetTester tester) async {
final List<String> logs = <String>[];
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}'),
child: const Text('test'),
),
),
);
await tester.fling(find.text('test'), const Offset(-10.0, 0.0), 1000.0, buttons: kSecondaryMouseButton);
await tester.pumpAndSettle();
const String b = '$kSecondaryMouseButton';
for(int i = 0; i < logs.length; i++) {
if (i == 0)
expect(logs[i], 'down $b');
else if (i != logs.length - 1)
expect(logs[i], 'move $b');
else
expect(logs[i], 'up 0');
}
},
);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment