From 43aee92e61af5ef2ce998d60c823af487e7c4466 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:17:53 -0800 Subject: [PATCH] Fix unresponsive mouse tooltip (#142282) Fixes https://github.com/flutter/flutter/issues/142045 The intent of using `??=` was that if the tooltip is already scheduled for showing, rescheduling another show does nothing. But if the tooltip is already scheduled for dismissing, the `??=` won't cancel the dismiss timer and as a result the tooltip won't show. So the `??=` is now replaced by `=` to keep it consistent with the `_scheduleDismissTooltip` implementation. --- .../flutter/lib/src/material/tooltip.dart | 3 +- .../flutter/test/material/tooltip_test.dart | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index c0ea84ce666..4431019283d 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -508,7 +508,8 @@ class TooltipState extends State with SingleTickerProviderStateMixin { ); switch (_controller.status) { case AnimationStatus.dismissed when withDelay.inMicroseconds > 0: - _timer ??= Timer(withDelay, show); + _timer?.cancel(); + _timer = Timer(withDelay, show); // If the tooltip is already fading in or fully visible, skip the // animation and show the tooltip immediately. case AnimationStatus.dismissed: diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 205dbad447f..2783b29a8e9 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -1562,6 +1562,89 @@ void main() { expect(find.text('last tooltip'), findsOneWidget); }); + // Regression test for https://github.com/flutter/flutter/issues/142045. + testWidgets('Tooltip shows/hides when the mouse hovers, and then exits and re-enters in quick succession', (WidgetTester tester) async { + const Duration waitDuration = Durations.extralong1; + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + addTearDown(() async { + return gesture.removePointer(); + }); + await gesture.addPointer(); + await gesture.moveTo(const Offset(1.0, 1.0)); + + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: Tooltip( + message: tooltipText, + waitDuration: waitDuration, + exitDuration: waitDuration, + child: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ), + ), + ); + + Future mouseEnterAndWaitUntilVisible() async { + await gesture.moveTo(tester.getCenter(find.byType(Tooltip))); + await tester.pump(); + await tester.pump(waitDuration); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsOne); + } + + Future mouseExit() async { + await gesture.moveTo(Offset.zero); + await tester.pump(); + } + + Future performSequence(Iterable Function()> actions) async { + for (final Future Function() action in actions) { + await action(); + } + } + + await performSequence( Function()>[mouseEnterAndWaitUntilVisible]); + expect(find.text(tooltipText), findsOne); + + // Wait for reset. + await mouseExit(); + await tester.pump(const Duration(hours: 1)); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsNothing); + + await performSequence( Function()>[ + mouseEnterAndWaitUntilVisible, + mouseExit, + mouseEnterAndWaitUntilVisible, + ]); + expect(find.text(tooltipText), findsOne); + + // Wait for reset. + await mouseExit(); + await tester.pump(const Duration(hours: 1)); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsNothing); + + await performSequence( Function()>[ + mouseEnterAndWaitUntilVisible, + mouseExit, + mouseEnterAndWaitUntilVisible, + mouseExit, + mouseEnterAndWaitUntilVisible, + ]); + expect(find.text(tooltipText), findsOne); + + // Wait for reset. + await mouseExit(); + await tester.pump(const Duration(hours: 1)); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsNothing); + }); + testWidgets('Tooltip text is also hoverable', (WidgetTester tester) async { const Duration waitDuration = Duration.zero; final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);