Resolving and piping the view ID through the WidgetController and the TestPointer so that clicks wind up on the right view (#178941)

## What's new?
- While coming up with a scheme for testing multi-window, I realize that
issuing taps in `testWidgets` across views was not working because the
view was always being set to `0`
- The fix is to resolve the view when we can, and provide an optional
view in the other methods

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Matthew Kosarek 2025-12-01 13:25:39 -05:00 committed by GitHub
parent 804fe2f3e2
commit a99fb28619
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 171 additions and 46 deletions

View File

@ -7,6 +7,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('Should route pointers', () {
var callbackRan = false;
void callback(PointerEvent event) {

View File

@ -776,9 +776,24 @@ abstract class WidgetController {
}
FlutterView _viewOf(finders.FinderBase<Element> finder) {
return firstWidget<View>(
final FlutterView? view = _maybeViewOf(finder);
if (view == null) {
throw StateError(
'No FlutterView ancestor found for finder: '
'${finder.toString(describeSelf: true)}',
);
}
return view;
}
FlutterView? _maybeViewOf(finders.FinderBase<Element> finder) {
final Iterable<View> views = widgetList<View>(
finders.find.ancestor(of: finder, matching: finders.find.byType(View)),
).view;
);
if (views.isEmpty) {
return null;
}
return views.first.view;
}
/// Checks if `finder` exists in the tree.
@ -1041,11 +1056,13 @@ abstract class WidgetController {
bool warnIfMissed = true,
PointerDeviceKind kind = PointerDeviceKind.touch,
}) {
final FlutterView? view = _maybeViewOf(finder);
return tapAt(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'tap'),
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
}
@ -1097,7 +1114,7 @@ abstract class WidgetController {
),
]);
}
return tapAt(tapLocation, pointer: pointer, buttons: buttons);
return tapAt(tapLocation, pointer: pointer, buttons: buttons, view: ranges.single.view.view);
}
/// Dispatch a pointer down / pointer up sequence at the given location.
@ -1106,6 +1123,7 @@ abstract class WidgetController {
int? pointer,
int buttons = kPrimaryButton,
PointerDeviceKind kind = PointerDeviceKind.touch,
FlutterView? view,
}) {
return TestAsyncUtils.guard<void>(() async {
final TestGesture gesture = await startGesture(
@ -1113,6 +1131,7 @@ abstract class WidgetController {
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
await gesture.up();
});
@ -1138,12 +1157,14 @@ abstract class WidgetController {
bool warnIfMissed = true,
PointerDeviceKind kind = PointerDeviceKind.touch,
}) {
final FlutterView? view = _maybeViewOf(finder);
return TestAsyncUtils.guard<TestGesture>(() {
return startGesture(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'press'),
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
});
}
@ -1167,11 +1188,13 @@ abstract class WidgetController {
bool warnIfMissed = true,
PointerDeviceKind kind = PointerDeviceKind.touch,
}) {
final FlutterView? view = _maybeViewOf(finder);
return longPressAt(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'longPress'),
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
}
@ -1182,6 +1205,7 @@ abstract class WidgetController {
int? pointer,
int buttons = kPrimaryButton,
PointerDeviceKind kind = PointerDeviceKind.touch,
FlutterView? view,
}) {
return TestAsyncUtils.guard<void>(() async {
final TestGesture gesture = await startGesture(
@ -1189,6 +1213,7 @@ abstract class WidgetController {
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
await pump(kLongPressTimeout + kPressTimeout);
await gesture.up();
@ -1253,6 +1278,7 @@ abstract class WidgetController {
bool warnIfMissed = true,
PointerDeviceKind deviceKind = PointerDeviceKind.touch,
}) {
final FlutterView? view = _maybeViewOf(finder);
return flingFrom(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'fling'),
offset,
@ -1263,6 +1289,7 @@ abstract class WidgetController {
initialOffset: initialOffset,
initialOffsetDelay: initialOffsetDelay,
deviceKind: deviceKind,
view: view,
);
}
@ -1283,6 +1310,7 @@ abstract class WidgetController {
Offset initialOffset = Offset.zero,
Duration initialOffsetDelay = const Duration(seconds: 1),
PointerDeviceKind deviceKind = PointerDeviceKind.touch,
FlutterView? view,
}) {
assert(offset.distance > 0.0);
assert(speed > 0.0); // speed is pixels/second
@ -1294,7 +1322,11 @@ abstract class WidgetController {
var timeStamp = 0.0;
var lastTimeStamp = timeStamp;
await sendEventToBinding(
testPointer.down(startLocation, timeStamp: Duration(microseconds: timeStamp.round())),
testPointer.down(
startLocation,
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
if (initialOffset.distance > 0.0) {
await sendEventToBinding(
@ -1310,7 +1342,11 @@ abstract class WidgetController {
final Offset location =
startLocation + initialOffset + Offset.lerp(Offset.zero, offset, i / kMoveCount)!;
await sendEventToBinding(
testPointer.move(location, timeStamp: Duration(microseconds: timeStamp.round())),
testPointer.move(
location,
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
timeStamp += timeStampDelta;
if (timeStamp - lastTimeStamp > frameInterval.inMicroseconds) {
@ -1319,7 +1355,10 @@ abstract class WidgetController {
}
}
await sendEventToBinding(
testPointer.up(timeStamp: Duration(microseconds: timeStamp.round())),
testPointer.up(
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
});
}
@ -1345,6 +1384,7 @@ abstract class WidgetController {
Duration initialOffsetDelay = const Duration(seconds: 1),
bool warnIfMissed = true,
}) {
final FlutterView? view = _maybeViewOf(finder);
return trackpadFlingFrom(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'fling'),
offset,
@ -1354,6 +1394,7 @@ abstract class WidgetController {
frameInterval: frameInterval,
initialOffset: initialOffset,
initialOffsetDelay: initialOffsetDelay,
view: view,
);
}
@ -1374,6 +1415,7 @@ abstract class WidgetController {
Duration frameInterval = const Duration(milliseconds: 16),
Offset initialOffset = Offset.zero,
Duration initialOffsetDelay = const Duration(seconds: 1),
FlutterView? view,
}) {
assert(offset.distance > 0.0);
assert(speed > 0.0); // speed is pixels/second
@ -1393,6 +1435,7 @@ abstract class WidgetController {
testPointer.panZoomStart(
startLocation,
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
if (initialOffset.distance > 0.0) {
@ -1401,6 +1444,7 @@ abstract class WidgetController {
startLocation,
pan: initialOffset,
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
timeStamp += initialOffsetDelay.inMicroseconds;
@ -1413,6 +1457,7 @@ abstract class WidgetController {
startLocation,
pan: pan,
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
timeStamp += timeStampDelta;
@ -1422,7 +1467,10 @@ abstract class WidgetController {
}
}
await sendEventToBinding(
testPointer.panZoomEnd(timeStamp: Duration(microseconds: timeStamp.round())),
testPointer.panZoomEnd(
timeStamp: Duration(microseconds: timeStamp.round()),
view: view,
),
);
});
}
@ -1532,6 +1580,7 @@ abstract class WidgetController {
bool warnIfMissed = true,
PointerDeviceKind kind = PointerDeviceKind.touch,
}) {
final FlutterView? view = _maybeViewOf(finder);
return dragFrom(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'drag'),
offset,
@ -1540,6 +1589,7 @@ abstract class WidgetController {
touchSlopX: touchSlopX,
touchSlopY: touchSlopY,
kind: kind,
view: view,
);
}
@ -1562,6 +1612,7 @@ abstract class WidgetController {
double touchSlopX = kDragSlopDefault,
double touchSlopY = kDragSlopDefault,
PointerDeviceKind kind = PointerDeviceKind.touch,
FlutterView? view,
}) {
assert(kDragSlopDefault > kTouchSlop);
return TestAsyncUtils.guard<void>(() async {
@ -1570,6 +1621,7 @@ abstract class WidgetController {
pointer: pointer,
buttons: buttons,
kind: kind,
view: view,
);
final double xSign = offset.dx.sign;
@ -1597,17 +1649,20 @@ abstract class WidgetController {
final double diffY = offsetSlope.abs() * touchSlopX * ySign;
// The vector from the origin to the vertical edge.
await gesture.moveBy(Offset(signedSlopX, diffY));
await gesture.moveBy(Offset(signedSlopX, diffY), view: view);
if (offsetY.abs() <= touchSlopY) {
// The drag ends on or before getting to the horizontal extension of the horizontal edge.
await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY));
await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY), view: view);
} else {
final double diffY2 = signedSlopY - diffY;
final double diffX2 = inverseOffsetSlope * diffY2;
// The vector from the edge of the box to the horizontal extension of the horizontal edge.
await gesture.moveBy(Offset(diffX2, diffY2));
await gesture.moveBy(Offset(offsetX - diffX2 - signedSlopX, offsetY - signedSlopY));
await gesture.moveBy(Offset(diffX2, diffY2), view: view);
await gesture.moveBy(
Offset(offsetX - diffX2 - signedSlopX, offsetY - signedSlopY),
view: view,
);
}
} else {
assert(offsetY.abs() > touchSlopY);
@ -1616,29 +1671,32 @@ abstract class WidgetController {
final double diffX = inverseOffsetSlope.abs() * touchSlopY * xSign;
// The vector from the origin to the vertical edge.
await gesture.moveBy(Offset(diffX, signedSlopY));
await gesture.moveBy(Offset(diffX, signedSlopY), view: view);
if (offsetX.abs() <= touchSlopX) {
// The drag ends on or before getting to the vertical extension of the vertical edge.
await gesture.moveBy(Offset(offsetX - diffX, offsetY - signedSlopY));
await gesture.moveBy(Offset(offsetX - diffX, offsetY - signedSlopY), view: view);
} else {
final double diffX2 = signedSlopX - diffX;
final double diffY2 = offsetSlope * diffX2;
// The vector from the edge of the box to the vertical extension of the vertical edge.
await gesture.moveBy(Offset(diffX2, diffY2));
await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY2 - signedSlopY));
await gesture.moveBy(Offset(diffX2, diffY2), view: view);
await gesture.moveBy(
Offset(offsetX - signedSlopX, offsetY - diffY2 - signedSlopY),
view: view,
);
}
}
} else {
// The drag goes through the corner of the box.
await gesture.moveBy(Offset(signedSlopX, signedSlopY));
await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - signedSlopY));
await gesture.moveBy(Offset(signedSlopX, signedSlopY), view: view);
await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - signedSlopY), view: view);
}
} else {
// The drag ends inside the box.
await gesture.moveBy(offset);
await gesture.moveBy(offset, view: view);
}
await gesture.up();
await gesture.up(view: view);
});
}
@ -1670,6 +1728,7 @@ abstract class WidgetController {
int buttons = kPrimaryButton,
double frequency = 60.0,
bool warnIfMissed = true,
FlutterView? view,
}) {
return timedDragFrom(
getCenter(finder, warnIfMissed: warnIfMissed, callee: 'timedDrag'),
@ -1678,6 +1737,7 @@ abstract class WidgetController {
pointer: pointer,
buttons: buttons,
frequency: frequency,
view: view,
);
}
@ -1696,6 +1756,7 @@ abstract class WidgetController {
int? pointer,
int buttons = kPrimaryButton,
double frequency = 60.0,
FlutterView? view,
}) {
assert(frequency > 0);
final int intervals = duration.inMicroseconds * frequency ~/ 1E6;
@ -1710,13 +1771,18 @@ abstract class WidgetController {
];
final records = <PointerEventRecord>[
PointerEventRecord(Duration.zero, <PointerEvent>[
PointerAddedEvent(position: startLocation),
PointerAddedEvent(
position: startLocation,
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
),
PointerDownEvent(position: startLocation, pointer: pointer, buttons: buttons),
]),
...<PointerEventRecord>[
for (int t = 0; t <= intervals; t += 1)
PointerEventRecord(timeStamps[t], <PointerEvent>[
PointerMoveEvent(
viewId:
view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamps[t],
position: offsets[t + 1],
delta: offsets[t + 1] - offsets[t],
@ -1727,6 +1793,7 @@ abstract class WidgetController {
],
PointerEventRecord(duration, <PointerEvent>[
PointerUpEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: duration,
position: offsets.last,
pointer: pointer,
@ -1802,12 +1869,13 @@ abstract class WidgetController {
int? pointer,
PointerDeviceKind kind = PointerDeviceKind.touch,
int buttons = kPrimaryButton,
FlutterView? view,
}) async {
final TestGesture result = _createGesture(pointer: pointer, kind: kind, buttons: buttons);
if (kind == PointerDeviceKind.trackpad) {
await result.panZoomStart(downLocation);
await result.panZoomStart(downLocation, view: view);
} else {
await result.down(downLocation);
await result.down(downLocation, view: view);
}
return result;
}

View File

@ -8,8 +8,8 @@
/// @docImport 'widget_tester.dart';
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'test_async_utils.dart';
@ -121,7 +121,12 @@ class TestPointer {
///
/// 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}) {
PointerDownEvent down(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int? buttons,
FlutterView? view,
}) {
assert(!isDown);
assert(!isPanZoomActive);
_isDown = true;
@ -130,6 +135,7 @@ class TestPointer {
_buttons = buttons;
}
return PointerDownEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -149,7 +155,12 @@ class TestPointer {
///
/// 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}) {
PointerMoveEvent move(
Offset newLocation, {
Duration timeStamp = Duration.zero,
int? buttons,
FlutterView? view,
}) {
assert(
isDown,
'Move events can only be generated when the pointer is down. To '
@ -164,6 +175,7 @@ class TestPointer {
}
return PointerMoveEvent(
timeStamp: timeStamp,
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
kind: kind,
device: _device,
pointer: pointer,
@ -179,11 +191,12 @@ class TestPointer {
/// specific time stamp by passing the `timeStamp` argument.
///
/// The object is no longer usable after this method has been called.
PointerUpEvent up({Duration timeStamp = Duration.zero}) {
PointerUpEvent up({Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(!isPanZoomActive);
assert(isDown);
_isDown = false;
return PointerUpEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -198,11 +211,12 @@ class TestPointer {
/// specific time stamp by passing the `timeStamp` argument.
///
/// The object is no longer usable after this method has been called.
PointerCancelEvent cancel({Duration timeStamp = Duration.zero}) {
PointerCancelEvent cancel({Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(isDown);
_isDown = false;
return PointerCancelEvent(
timeStamp: timeStamp,
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
kind: kind,
device: _device,
pointer: pointer,
@ -215,9 +229,14 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerAddedEvent addPointer({Duration timeStamp = Duration.zero, Offset? location}) {
PointerAddedEvent addPointer({
Duration timeStamp = Duration.zero,
Offset? location,
FlutterView? view,
}) {
_location = location ?? _location;
return PointerAddedEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -230,9 +249,14 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerRemovedEvent removePointer({Duration timeStamp = Duration.zero, Offset? location}) {
PointerRemovedEvent removePointer({
Duration timeStamp = Duration.zero,
Offset? location,
FlutterView? view,
}) {
_location = location ?? _location;
return PointerRemovedEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -248,7 +272,11 @@ class TestPointer {
///
/// [isDown] must be false, since hover events can't be sent when the pointer
/// is up.
PointerHoverEvent hover(Offset newLocation, {Duration timeStamp = Duration.zero}) {
PointerHoverEvent hover(
Offset newLocation, {
Duration timeStamp = Duration.zero,
FlutterView? view,
}) {
assert(
!isDown,
'Hover events can only be generated when the pointer is up. To '
@ -257,6 +285,7 @@ class TestPointer {
final Offset delta = location != null ? newLocation - location! : Offset.zero;
_location = newLocation;
return PointerHoverEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -275,10 +304,12 @@ class TestPointer {
Offset scrollDelta, {
Duration timeStamp = Duration.zero,
RespondPointerEventCallback? onRespond,
FlutterView? view,
}) {
assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events");
assert(location != null);
return PointerScrollEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -292,10 +323,14 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerScrollInertiaCancelEvent scrollInertiaCancel({Duration timeStamp = Duration.zero}) {
PointerScrollInertiaCancelEvent scrollInertiaCancel({
Duration timeStamp = Duration.zero,
FlutterView? view,
}) {
assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events");
assert(location != null);
return PointerScrollInertiaCancelEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -307,10 +342,11 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerScaleEvent scale(double scale, {Duration timeStamp = Duration.zero}) {
PointerScaleEvent scale(double scale, {Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(kind != PointerDeviceKind.touch, "Touch pointers can't generate pointer signal events");
assert(location != null);
return PointerScaleEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
kind: kind,
device: _device,
@ -324,13 +360,18 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerPanZoomStartEvent panZoomStart(Offset location, {Duration timeStamp = Duration.zero}) {
PointerPanZoomStartEvent panZoomStart(
Offset location, {
Duration timeStamp = Duration.zero,
FlutterView? view,
}) {
assert(!isPanZoomActive);
assert(kind == PointerDeviceKind.trackpad);
_location = location;
_pan = Offset.zero;
_isPanZoomActive = true;
return PointerPanZoomStartEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
device: _device,
pointer: pointer,
@ -351,6 +392,7 @@ class TestPointer {
double scale = 1,
double rotation = 0,
Duration timeStamp = Duration.zero,
FlutterView? view,
}) {
assert(isPanZoomActive);
assert(kind == PointerDeviceKind.trackpad);
@ -358,6 +400,7 @@ class TestPointer {
final Offset panDelta = pan - _pan!;
_pan = pan;
return PointerPanZoomUpdateEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
device: _device,
pointer: pointer,
@ -374,12 +417,13 @@ class TestPointer {
///
/// By default, the time stamp on the event is [Duration.zero]. You can give a
/// specific time stamp by passing the `timeStamp` argument.
PointerPanZoomEndEvent panZoomEnd({Duration timeStamp = Duration.zero}) {
PointerPanZoomEndEvent panZoomEnd({Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(isPanZoomActive);
assert(kind == PointerDeviceKind.trackpad);
_isPanZoomActive = false;
_pan = null;
return PointerPanZoomEndEvent(
viewId: view?.viewId ?? WidgetsBinding.instance.platformDispatcher.implicitView!.viewId,
timeStamp: timeStamp,
device: _device,
pointer: pointer,
@ -428,13 +472,17 @@ class TestGesture {
/// Dispatch a pointer down event at the given `downLocation`, caching the
/// hit test result.
Future<void> down(Offset downLocation, {Duration timeStamp = Duration.zero}) async {
Future<void> down(
Offset downLocation, {
Duration timeStamp = Duration.zero,
FlutterView? view,
}) async {
assert(
_pointer.kind != PointerDeviceKind.trackpad,
'Trackpads are expected to send panZoomStart events, not down events.',
);
return TestAsyncUtils.guard<void>(() async {
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp));
return _dispatcher(_pointer.down(downLocation, timeStamp: timeStamp, view: view));
});
}
@ -491,16 +539,17 @@ class TestGesture {
/// * [WidgetController.timedDrag], a method to simulate the drag of a given widget in a given duration.
/// 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<void> moveBy(Offset offset, {Duration timeStamp = Duration.zero}) {
Future<void> moveBy(Offset offset, {Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(_pointer.location != null);
if (_pointer.isPanZoomActive) {
return panZoomUpdate(
_pointer.location!,
pan: (_pointer.pan ?? Offset.zero) + offset,
timeStamp: timeStamp,
view: view,
);
} else {
return moveTo(_pointer.location! + offset, timeStamp: timeStamp);
return moveTo(_pointer.location! + offset, timeStamp: timeStamp, view: view);
}
}
@ -514,28 +563,28 @@ class TestGesture {
/// * [WidgetController.timedDrag], a method to simulate the drag of a given widget in a given duration.
/// 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<void> moveTo(Offset location, {Duration timeStamp = Duration.zero}) {
Future<void> moveTo(Offset location, {Duration timeStamp = Duration.zero, FlutterView? view}) {
assert(_pointer.kind != PointerDeviceKind.trackpad);
return TestAsyncUtils.guard<void>(() {
if (_pointer._isDown) {
return _dispatcher(_pointer.move(location, timeStamp: timeStamp));
return _dispatcher(_pointer.move(location, timeStamp: timeStamp, view: view));
} else {
return _dispatcher(_pointer.hover(location, timeStamp: timeStamp));
return _dispatcher(_pointer.hover(location, timeStamp: timeStamp, view: view));
}
});
}
/// End the gesture by releasing the pointer. For trackpad pointers this
/// will send a panZoomEnd event instead of an up event.
Future<void> up({Duration timeStamp = Duration.zero}) {
Future<void> up({Duration timeStamp = Duration.zero, FlutterView? view}) {
return TestAsyncUtils.guard<void>(() async {
if (_pointer.kind == PointerDeviceKind.trackpad) {
assert(_pointer._isPanZoomActive);
await _dispatcher(_pointer.panZoomEnd(timeStamp: timeStamp));
await _dispatcher(_pointer.panZoomEnd(timeStamp: timeStamp, view: view));
assert(!_pointer._isPanZoomActive);
} else {
assert(_pointer._isDown);
await _dispatcher(_pointer.up(timeStamp: timeStamp));
await _dispatcher(_pointer.up(timeStamp: timeStamp, view: view));
assert(!_pointer._isDown);
}
});
@ -555,13 +604,17 @@ class TestGesture {
/// Dispatch a pointer pan zoom start event at the given `location`, caching the
/// hit test result.
Future<void> panZoomStart(Offset location, {Duration timeStamp = Duration.zero}) async {
Future<void> panZoomStart(
Offset location, {
Duration timeStamp = Duration.zero,
FlutterView? view,
}) async {
assert(
_pointer.kind == PointerDeviceKind.trackpad,
'Only trackpads can send PointerPanZoom events.',
);
return TestAsyncUtils.guard<void>(() async {
return _dispatcher(_pointer.panZoomStart(location, timeStamp: timeStamp));
return _dispatcher(_pointer.panZoomStart(location, timeStamp: timeStamp, view: view));
});
}
@ -573,6 +626,7 @@ class TestGesture {
double scale = 1,
double rotation = 0,
Duration timeStamp = Duration.zero,
FlutterView? view,
}) async {
assert(
_pointer.kind == PointerDeviceKind.trackpad,
@ -586,6 +640,7 @@ class TestGesture {
scale: scale,
rotation: rotation,
timeStamp: timeStamp,
view: view,
),
);
});