Unverified Commit 4d73448b authored by Jia Hao's avatar Jia Hao Committed by GitHub

[flutter_test] Add flag to send device pointer events to the framework (#108430)

parent dbadee00
......@@ -494,9 +494,25 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
///
/// When [handlePointerEvent] is called directly, [pointerEventSource]
/// is [TestBindingEventSource.device].
///
/// This means that pointer events triggered by the [WidgetController] (e.g.
/// via [WidgetController.tap]) will result in actual interactions with the
/// UI, but other pointer events such as those from physical taps will be
/// dropped. See also [shouldPropagateDevicePointerEvents] if this is
/// undesired.
TestBindingEventSource get pointerEventSource => _pointerEventSource;
TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;
/// Whether pointer events from [TestBindingEventSource.device] will be
/// propagated to the framework, or dropped.
///
/// Setting this can be useful to interact with the app in some other way
/// besides through the [WidgetController], such as with `adb shell input tap`
/// on Android.
///
/// See also [pointerEventSource].
bool shouldPropagateDevicePointerEvents = false;
/// Dispatch an event to the targets found by a hit test on its position,
/// and remember its source as [pointerEventSource].
///
......@@ -836,6 +852,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
final bool shouldPropagateDevicePointerEventsBeforeTest = shouldPropagateDevicePointerEvents;
// run the test
await testBody();
......@@ -854,6 +871,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
_verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
_verifyShouldPropagateDevicePointerEventsUnset(shouldPropagateDevicePointerEventsBeforeTest);
_verifyInvariants();
}
......@@ -943,6 +961,21 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
}());
}
void _verifyShouldPropagateDevicePointerEventsUnset(bool valueBeforeTest) {
assert(() {
if (shouldPropagateDevicePointerEvents != valueBeforeTest) {
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError(
'The value of shouldPropagateDevicePointerEvents was changed by the test.',
),
stack: StackTrace.current,
library: 'Flutter test framework',
));
}
return true;
}());
}
/// Called by the [testWidgets] function after a test is executed.
void postTest() {
assert(inTest);
......@@ -1595,7 +1628,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
///
/// Normally, device events are silently dropped. However, if this property is
/// set to a non-null value, then the events will be routed to its
/// [HitTestDispatcher.dispatchEvent] method instead.
/// [HitTestDispatcher.dispatchEvent] method instead, unless
/// [shouldPropagateDevicePointerEvents] is true.
///
/// Events dispatched by [TestGesture] are not affected by this.
HitTestDispatcher? deviceEventDispatcher;
......@@ -1630,6 +1664,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
super.handlePointerEvent(event);
break;
case TestBindingEventSource.device:
if (shouldPropagateDevicePointerEvents) {
super.handlePointerEvent(event);
break;
}
if (deviceEventDispatcher != null) {
// The pointer events received with this source has a global position
// (see [handlePointerEventForSource]). Transform it to the local
......@@ -1651,6 +1689,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
break;
case TestBindingEventSource.device:
assert(hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent);
if (shouldPropagateDevicePointerEvents) {
super.dispatchEvent(event, hitTestResult);
break;
}
assert(deviceEventDispatcher != null);
if (hitTestResult != null) {
deviceEventDispatcher!.dispatchEvent(event, hitTestResult);
......
......@@ -502,6 +502,10 @@ Future<void> expectLater(
///
/// For convenience, instances of this class (such as the one provided by
/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects.
///
/// When the binding is [LiveTestWidgetsFlutterBinding], events from
/// [LiveTestWidgetsFlutterBinding.deviceEventDispatcher] will be handled in
/// [dispatchEvent].
class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
WidgetTester._(super.binding) {
if (binding is LiveTestWidgetsFlutterBinding) {
......@@ -817,6 +821,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
}
/// Handler for device events caught by the binding in live test mode.
///
/// [PointerDownEvent]s received here will only print a diagnostic message
/// showing possible [Finder]s that can be used to interact with the widget at
/// the location of [result].
@override
void dispatchEvent(PointerEvent event, HitTestResult result) {
if (event is PointerDownEvent) {
......
......@@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
// This file is for testings that require a `LiveTestWidgetsFlutterBinding`
void main() {
LiveTestWidgetsFlutterBinding();
final LiveTestWidgetsFlutterBinding binding = LiveTestWidgetsFlutterBinding();
testWidgets('Input PointerAddedEvent', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: Text('Test')));
await tester.pump();
......@@ -99,4 +99,57 @@ void main() {
await expectLater(tester.binding.reassembleApplication(), completes);
}, timeout: const Timeout(Duration(seconds: 30)));
testWidgets('shouldPropagateDevicePointerEvents can override events from ${TestBindingEventSource.device}', (WidgetTester tester) async {
binding.shouldPropagateDevicePointerEvents = true;
await tester.pumpWidget(_ShowNumTaps());
final Offset position = tester.getCenter(find.text('0'));
// Simulates a real device tap.
//
// `handlePointerEventForSource defaults to sending events using
// TestBindingEventSource.device. This will not be forwarded to the actual
// gesture handlers, unless `shouldPropagateDevicePointerEvents` is true.
binding.handlePointerEventForSource(
PointerDownEvent(position: position),
);
binding.handlePointerEventForSource(
PointerUpEvent(position: position),
);
await tester.pump();
expect(find.text('1'), findsOneWidget);
// Reset the value, otherwise the test will fail when it checks that this
// has not been changed as an invariant.
binding.shouldPropagateDevicePointerEvents = false;
});
}
/// A widget that shows the number of times it has been tapped.
class _ShowNumTaps extends StatefulWidget {
@override
_ShowNumTapsState createState() => _ShowNumTapsState();
}
class _ShowNumTapsState extends State<_ShowNumTaps> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_counter++;
});
},
child: Directionality(
textDirection: TextDirection.ltr,
child: Text(_counter.toString()),
),
);
}
}
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