[flutter_test] Add flag to send device pointer events to the framework (#108430)
This commit is contained in:
parent
dbadee003f
commit
4d73448b52
@ -494,9 +494,25 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
///
|
///
|
||||||
/// When [handlePointerEvent] is called directly, [pointerEventSource]
|
/// When [handlePointerEvent] is called directly, [pointerEventSource]
|
||||||
/// is [TestBindingEventSource.device].
|
/// 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 get pointerEventSource => _pointerEventSource;
|
||||||
TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;
|
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,
|
/// Dispatch an event to the targets found by a hit test on its position,
|
||||||
/// and remember its source as [pointerEventSource].
|
/// and remember its source as [pointerEventSource].
|
||||||
///
|
///
|
||||||
@ -836,6 +852,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
|
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
|
||||||
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
|
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
|
||||||
final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
|
final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
|
||||||
|
final bool shouldPropagateDevicePointerEventsBeforeTest = shouldPropagateDevicePointerEvents;
|
||||||
|
|
||||||
// run the test
|
// run the test
|
||||||
await testBody();
|
await testBody();
|
||||||
@ -854,6 +871,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
|
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
|
||||||
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
|
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
|
||||||
_verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
|
_verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
|
||||||
|
_verifyShouldPropagateDevicePointerEventsUnset(shouldPropagateDevicePointerEventsBeforeTest);
|
||||||
_verifyInvariants();
|
_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.
|
/// Called by the [testWidgets] function after a test is executed.
|
||||||
void postTest() {
|
void postTest() {
|
||||||
assert(inTest);
|
assert(inTest);
|
||||||
@ -1595,7 +1628,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
///
|
///
|
||||||
/// Normally, device events are silently dropped. However, if this property is
|
/// 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
|
/// 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.
|
/// Events dispatched by [TestGesture] are not affected by this.
|
||||||
HitTestDispatcher? deviceEventDispatcher;
|
HitTestDispatcher? deviceEventDispatcher;
|
||||||
@ -1630,6 +1664,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
super.handlePointerEvent(event);
|
super.handlePointerEvent(event);
|
||||||
break;
|
break;
|
||||||
case TestBindingEventSource.device:
|
case TestBindingEventSource.device:
|
||||||
|
if (shouldPropagateDevicePointerEvents) {
|
||||||
|
super.handlePointerEvent(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (deviceEventDispatcher != null) {
|
if (deviceEventDispatcher != null) {
|
||||||
// The pointer events received with this source has a global position
|
// The pointer events received with this source has a global position
|
||||||
// (see [handlePointerEventForSource]). Transform it to the local
|
// (see [handlePointerEventForSource]). Transform it to the local
|
||||||
@ -1651,6 +1689,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
break;
|
break;
|
||||||
case TestBindingEventSource.device:
|
case TestBindingEventSource.device:
|
||||||
assert(hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent);
|
assert(hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent);
|
||||||
|
if (shouldPropagateDevicePointerEvents) {
|
||||||
|
super.dispatchEvent(event, hitTestResult);
|
||||||
|
break;
|
||||||
|
}
|
||||||
assert(deviceEventDispatcher != null);
|
assert(deviceEventDispatcher != null);
|
||||||
if (hitTestResult != null) {
|
if (hitTestResult != null) {
|
||||||
deviceEventDispatcher!.dispatchEvent(event, hitTestResult);
|
deviceEventDispatcher!.dispatchEvent(event, hitTestResult);
|
||||||
|
@ -502,6 +502,10 @@ Future<void> expectLater(
|
|||||||
///
|
///
|
||||||
/// For convenience, instances of this class (such as the one provided by
|
/// For convenience, instances of this class (such as the one provided by
|
||||||
/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects.
|
/// `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 {
|
class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
|
||||||
WidgetTester._(super.binding) {
|
WidgetTester._(super.binding) {
|
||||||
if (binding is LiveTestWidgetsFlutterBinding) {
|
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.
|
/// 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
|
@override
|
||||||
void dispatchEvent(PointerEvent event, HitTestResult result) {
|
void dispatchEvent(PointerEvent event, HitTestResult result) {
|
||||||
if (event is PointerDownEvent) {
|
if (event is PointerDownEvent) {
|
||||||
|
@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
|
|
||||||
// This file is for testings that require a `LiveTestWidgetsFlutterBinding`
|
// This file is for testings that require a `LiveTestWidgetsFlutterBinding`
|
||||||
void main() {
|
void main() {
|
||||||
LiveTestWidgetsFlutterBinding();
|
final LiveTestWidgetsFlutterBinding binding = LiveTestWidgetsFlutterBinding();
|
||||||
testWidgets('Input PointerAddedEvent', (WidgetTester tester) async {
|
testWidgets('Input PointerAddedEvent', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(const MaterialApp(home: Text('Test')));
|
await tester.pumpWidget(const MaterialApp(home: Text('Test')));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -99,4 +99,57 @@ void main() {
|
|||||||
|
|
||||||
await expectLater(tester.binding.reassembleApplication(), completes);
|
await expectLater(tester.binding.reassembleApplication(), completes);
|
||||||
}, timeout: const Timeout(Duration(seconds: 30)));
|
}, 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()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user