From 5398f5c566e00c70ffdcc7dc84d7683226940684 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 29 Jul 2020 14:47:06 -0700 Subject: [PATCH] Add tests for platform views' hover behavior (#61667) --- .../lib/src/rendering/platform_view.dart | 19 ++- .../test/rendering/platform_view_test.dart | 1 + .../test/widgets/platform_view_test.dart | 140 ++++++++++++++++++ 3 files changed, 154 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index af1eab03ee..bc8f0bc593 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; +import 'binding.dart'; import 'box.dart'; import 'layer.dart'; import 'mouse_cursor.dart'; @@ -662,9 +663,15 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin { mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation { /// How to behave during hit testing. - // The implicit setter is enough here as changing this value will just affect - // any newly arriving events there's nothing we need to invalidate. - PlatformViewHitTestBehavior hitTestBehavior; + // Changing _hitTestBehavior might affect which objects are considered hovered over. + set hitTestBehavior(PlatformViewHitTestBehavior value) { + if (value != _hitTestBehavior) { + _hitTestBehavior = value; + if (owner != null) + RendererBinding.instance.mouseTracker.schedulePostFrameCheck(); + } + } + PlatformViewHitTestBehavior _hitTestBehavior; _HandlePointerEvent _handlePointerEvent; @@ -690,15 +697,15 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation { @override bool hitTest(BoxHitTestResult result, { Offset position }) { - if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) { + if (_hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) { return false; } result.add(BoxHitTestEntry(this, position)); - return hitTestBehavior == PlatformViewHitTestBehavior.opaque; + return _hitTestBehavior == PlatformViewHitTestBehavior.opaque; } @override - bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent; + bool hitTestSelf(Offset position) => _hitTestBehavior != PlatformViewHitTestBehavior.transparent; @override PointerEnterEventListener get onEnter => null; diff --git a/packages/flutter/test/rendering/platform_view_test.dart b/packages/flutter/test/rendering/platform_view_test.dart index b476a8b159..92268994fc 100644 --- a/packages/flutter/test/rendering/platform_view_test.dart +++ b/packages/flutter/test/rendering/platform_view_test.dart @@ -19,6 +19,7 @@ void main() { FakePlatformViewController fakePlatformViewController; PlatformViewRenderBox platformViewRenderBox; setUp(() { + renderer; // Initialize bindings fakePlatformViewController = FakePlatformViewController(0); platformViewRenderBox = PlatformViewRenderBox( controller: fakePlatformViewController, diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart index d4a5661319..252eed0307 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -2433,4 +2433,144 @@ void main() { expect(controller.focusCleared, true); }); }); + + testWidgets('Platform views respect hitTestBehavior', (WidgetTester tester) async { + final FakePlatformViewController controller = FakePlatformViewController(0); + + final List logs = []; + + // ------------------------- + // | MouseRegion1 | MouseRegion1 + // | |-----------------| | | + // | | MouseRegion2 | | |- Stack + // | | |---------| | | | + // | | |Platform | | | |- MouseRegion2 + // | | |View | | | |- PlatformView + // | | |---------| | | + // | | | | + // | |-----------------| | + // | | + // ------------------------- + Widget scaffold(Widget target) { + return Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + width: 600, + height: 600, + child: MouseRegion( + onEnter: (_) { logs.add('enter1'); }, + onExit: (_) { logs.add('exit1'); }, + cursor: SystemMouseCursors.forbidden, + child: Stack( + children: [ + Center( + child: SizedBox( + width: 400, + height: 400, + child: MouseRegion( + onEnter: (_) { logs.add('enter2'); }, + onExit: (_) { logs.add('exit2'); }, + cursor: SystemMouseCursors.text, + ), + ), + ), + Center( + child: SizedBox( + width: 200, + height: 200, + child: target, + ), + ), + ], + ) + ), + ), + ), + ); + } + + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 0); + addTearDown(gesture.removePointer); + + // Test: Opaque + await tester.pumpWidget( + scaffold(PlatformViewSurface( + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: const >{} + )) + ); + logs.clear(); + + await gesture.moveTo(const Offset(400, 300)); + expect(logs, ['enter1']); + expect(controller.dispatchedPointerEvents, hasLength(1)); + expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent); + logs.clear(); + controller.dispatchedPointerEvents.clear(); + + // Test: changing no option does not trigger events + await tester.pumpWidget( + scaffold(PlatformViewSurface( + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: const >{} + )) + ); + expect(logs, isEmpty); + expect(controller.dispatchedPointerEvents, isEmpty); + + // Test: Transluscent + await tester.pumpWidget( + scaffold(PlatformViewSurface( + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.translucent, + gestureRecognizers: const >{} + )) + ); + expect(logs, ['enter2']); + expect(controller.dispatchedPointerEvents, isEmpty); + logs.clear(); + + await gesture.moveBy(const Offset(1, 1)); + expect(logs, isEmpty); + expect(controller.dispatchedPointerEvents, hasLength(1)); + expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent); + expect(controller.dispatchedPointerEvents[0].position, const Offset(401, 301)); + expect(controller.dispatchedPointerEvents[0].localPosition, const Offset(101, 101)); + controller.dispatchedPointerEvents.clear(); + + // Test: Transparent + await tester.pumpWidget( + scaffold(PlatformViewSurface( + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.transparent, + gestureRecognizers: const >{} + )) + ); + expect(logs, isEmpty); + expect(controller.dispatchedPointerEvents, isEmpty); + + await gesture.moveBy(const Offset(1, 1)); + expect(logs, isEmpty); + expect(controller.dispatchedPointerEvents, isEmpty); + + // Test: Back to opaque + await tester.pumpWidget( + scaffold(PlatformViewSurface( + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: const >{} + )) + ); + expect(logs, ['exit2']); + expect(controller.dispatchedPointerEvents, isEmpty); + logs.clear(); + + await gesture.moveBy(const Offset(1, 1)); + expect(logs, isEmpty); + expect(controller.dispatchedPointerEvents, hasLength(1)); + expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent); + }); }