Skip to content

Commit 5398f5c

Browse files
authored
Add tests for platform views' hover behavior (flutter#61667)
1 parent 88a7d83 commit 5398f5c

File tree

3 files changed

+154
-6
lines changed

3 files changed

+154
-6
lines changed

packages/flutter/lib/src/rendering/platform_view.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart';
1212
import 'package:flutter/semantics.dart';
1313
import 'package:flutter/services.dart';
1414

15+
import 'binding.dart';
1516
import 'box.dart';
1617
import 'layer.dart';
1718
import 'mouse_cursor.dart';
@@ -662,9 +663,15 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
662663
mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
663664

664665
/// How to behave during hit testing.
665-
// The implicit setter is enough here as changing this value will just affect
666-
// any newly arriving events there's nothing we need to invalidate.
667-
PlatformViewHitTestBehavior hitTestBehavior;
666+
// Changing _hitTestBehavior might affect which objects are considered hovered over.
667+
set hitTestBehavior(PlatformViewHitTestBehavior value) {
668+
if (value != _hitTestBehavior) {
669+
_hitTestBehavior = value;
670+
if (owner != null)
671+
RendererBinding.instance.mouseTracker.schedulePostFrameCheck();
672+
}
673+
}
674+
PlatformViewHitTestBehavior _hitTestBehavior;
668675

669676
_HandlePointerEvent _handlePointerEvent;
670677

@@ -690,15 +697,15 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
690697

691698
@override
692699
bool hitTest(BoxHitTestResult result, { Offset position }) {
693-
if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) {
700+
if (_hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) {
694701
return false;
695702
}
696703
result.add(BoxHitTestEntry(this, position));
697-
return hitTestBehavior == PlatformViewHitTestBehavior.opaque;
704+
return _hitTestBehavior == PlatformViewHitTestBehavior.opaque;
698705
}
699706

700707
@override
701-
bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent;
708+
bool hitTestSelf(Offset position) => _hitTestBehavior != PlatformViewHitTestBehavior.transparent;
702709

703710
@override
704711
PointerEnterEventListener get onEnter => null;

packages/flutter/test/rendering/platform_view_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ void main() {
1919
FakePlatformViewController fakePlatformViewController;
2020
PlatformViewRenderBox platformViewRenderBox;
2121
setUp(() {
22+
renderer; // Initialize bindings
2223
fakePlatformViewController = FakePlatformViewController(0);
2324
platformViewRenderBox = PlatformViewRenderBox(
2425
controller: fakePlatformViewController,

packages/flutter/test/widgets/platform_view_test.dart

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,4 +2433,144 @@ void main() {
24332433
expect(controller.focusCleared, true);
24342434
});
24352435
});
2436+
2437+
testWidgets('Platform views respect hitTestBehavior', (WidgetTester tester) async {
2438+
final FakePlatformViewController controller = FakePlatformViewController(0);
2439+
2440+
final List<String> logs = <String>[];
2441+
2442+
// -------------------------
2443+
// | MouseRegion1 | MouseRegion1
2444+
// | |-----------------| | |
2445+
// | | MouseRegion2 | | |- Stack
2446+
// | | |---------| | | |
2447+
// | | |Platform | | | |- MouseRegion2
2448+
// | | |View | | | |- PlatformView
2449+
// | | |---------| | |
2450+
// | | | |
2451+
// | |-----------------| |
2452+
// | |
2453+
// -------------------------
2454+
Widget scaffold(Widget target) {
2455+
return Directionality(
2456+
textDirection: TextDirection.ltr,
2457+
child: Center(
2458+
child: SizedBox(
2459+
width: 600,
2460+
height: 600,
2461+
child: MouseRegion(
2462+
onEnter: (_) { logs.add('enter1'); },
2463+
onExit: (_) { logs.add('exit1'); },
2464+
cursor: SystemMouseCursors.forbidden,
2465+
child: Stack(
2466+
children: <Widget>[
2467+
Center(
2468+
child: SizedBox(
2469+
width: 400,
2470+
height: 400,
2471+
child: MouseRegion(
2472+
onEnter: (_) { logs.add('enter2'); },
2473+
onExit: (_) { logs.add('exit2'); },
2474+
cursor: SystemMouseCursors.text,
2475+
),
2476+
),
2477+
),
2478+
Center(
2479+
child: SizedBox(
2480+
width: 200,
2481+
height: 200,
2482+
child: target,
2483+
),
2484+
),
2485+
],
2486+
)
2487+
),
2488+
),
2489+
),
2490+
);
2491+
}
2492+
2493+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 0);
2494+
addTearDown(gesture.removePointer);
2495+
2496+
// Test: Opaque
2497+
await tester.pumpWidget(
2498+
scaffold(PlatformViewSurface(
2499+
controller: controller,
2500+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
2501+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}
2502+
))
2503+
);
2504+
logs.clear();
2505+
2506+
await gesture.moveTo(const Offset(400, 300));
2507+
expect(logs, <String>['enter1']);
2508+
expect(controller.dispatchedPointerEvents, hasLength(1));
2509+
expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
2510+
logs.clear();
2511+
controller.dispatchedPointerEvents.clear();
2512+
2513+
// Test: changing no option does not trigger events
2514+
await tester.pumpWidget(
2515+
scaffold(PlatformViewSurface(
2516+
controller: controller,
2517+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
2518+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}
2519+
))
2520+
);
2521+
expect(logs, isEmpty);
2522+
expect(controller.dispatchedPointerEvents, isEmpty);
2523+
2524+
// Test: Transluscent
2525+
await tester.pumpWidget(
2526+
scaffold(PlatformViewSurface(
2527+
controller: controller,
2528+
hitTestBehavior: PlatformViewHitTestBehavior.translucent,
2529+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}
2530+
))
2531+
);
2532+
expect(logs, <String>['enter2']);
2533+
expect(controller.dispatchedPointerEvents, isEmpty);
2534+
logs.clear();
2535+
2536+
await gesture.moveBy(const Offset(1, 1));
2537+
expect(logs, isEmpty);
2538+
expect(controller.dispatchedPointerEvents, hasLength(1));
2539+
expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
2540+
expect(controller.dispatchedPointerEvents[0].position, const Offset(401, 301));
2541+
expect(controller.dispatchedPointerEvents[0].localPosition, const Offset(101, 101));
2542+
controller.dispatchedPointerEvents.clear();
2543+
2544+
// Test: Transparent
2545+
await tester.pumpWidget(
2546+
scaffold(PlatformViewSurface(
2547+
controller: controller,
2548+
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
2549+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}
2550+
))
2551+
);
2552+
expect(logs, isEmpty);
2553+
expect(controller.dispatchedPointerEvents, isEmpty);
2554+
2555+
await gesture.moveBy(const Offset(1, 1));
2556+
expect(logs, isEmpty);
2557+
expect(controller.dispatchedPointerEvents, isEmpty);
2558+
2559+
// Test: Back to opaque
2560+
await tester.pumpWidget(
2561+
scaffold(PlatformViewSurface(
2562+
controller: controller,
2563+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
2564+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}
2565+
))
2566+
);
2567+
expect(logs, <String>['exit2']);
2568+
expect(controller.dispatchedPointerEvents, isEmpty);
2569+
logs.clear();
2570+
2571+
await gesture.moveBy(const Offset(1, 1));
2572+
expect(logs, isEmpty);
2573+
expect(controller.dispatchedPointerEvents, hasLength(1));
2574+
expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
2575+
});
24362576
}

0 commit comments

Comments
 (0)