Skip to content

Commit e0b2138

Browse files
authored
Dispose OverlayEntry in TooltipState. (#117291)
1 parent fc8ea56 commit e0b2138

File tree

3 files changed

+125
-45
lines changed

3 files changed

+125
-45
lines changed

packages/flutter/lib/src/material/tooltip.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
630630
_entry?.remove();
631631
}
632632
_isConcealed = false;
633+
_entry?.dispose();
633634
_entry = null;
634635
if (_mouseIsConnected) {
635636
Tooltip._revealLastTooltip();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:leak_tracker/leak_tracker.dart';
8+
9+
typedef LeaksObtainer = void Function(Leaks foundLeaks);
10+
11+
/// Wrapper for [withLeakTracking] with Flutter specific functionality.
12+
///
13+
/// The method will fail if wrapped code contains memory leaks.
14+
///
15+
/// See details in documentation for `withLeakTracking` at
16+
/// https://github.com/dart-lang/leak_tracker/blob/main/lib/src/orchestration.dart#withLeakTracking
17+
///
18+
/// The Flutter related enhancements are:
19+
/// 1. Listens to [MemoryAllocations] events.
20+
/// 2. Uses `tester.runAsync` for leak detection if [tester] is provided.
21+
///
22+
/// If you use [testWidgets], pass [tester] to avoid async issues in leak processing.
23+
/// Pass null otherwise.
24+
///
25+
/// Pass [leaksObtainer] if you want to get leak information before
26+
/// the method failure.
27+
Future<void> withFlutterLeakTracking(
28+
DartAsyncCallback callback, {
29+
required WidgetTester? tester,
30+
StackTraceCollectionConfig stackTraceCollectionConfig =
31+
const StackTraceCollectionConfig(),
32+
Duration? timeoutForFinalGarbageCollection,
33+
LeaksObtainer? leaksObtainer,
34+
}) async {
35+
// The method is copied (with improvements) from
36+
// `package:leak_tracker/test/test_infra/flutter_helpers.dart`.
37+
38+
// The method is not combined with [testWidgets], because the combining will
39+
// impact VSCode's ability to recognize tests.
40+
41+
// Leak tracker does not work for web platform.
42+
if (kIsWeb) {
43+
await callback();
44+
return;
45+
}
46+
47+
void flutterEventToLeakTracker(ObjectEvent event) {
48+
return dispatchObjectEvent(event.toMap());
49+
}
50+
51+
return TestAsyncUtils.guard<void>(() async {
52+
MemoryAllocations.instance.addListener(flutterEventToLeakTracker);
53+
final AsyncCodeRunner asyncCodeRunner = tester == null
54+
? (DartAsyncCallback action) async => action()
55+
: (DartAsyncCallback action) async => tester.runAsync(action);
56+
try {
57+
final Leaks leaks = await withLeakTracking(
58+
callback,
59+
asyncCodeRunner: asyncCodeRunner,
60+
stackTraceCollectionConfig: stackTraceCollectionConfig,
61+
shouldThrowOnLeaks: false,
62+
timeoutForFinalGarbageCollection: timeoutForFinalGarbageCollection,
63+
);
64+
if (leaksObtainer != null) {
65+
leaksObtainer(leaks);
66+
}
67+
expect(leaks, isLeakFree);
68+
} finally {
69+
MemoryAllocations.instance.removeListener(flutterEventToLeakTracker);
70+
}
71+
});
72+
}

packages/flutter/test/material/tooltip_test.dart

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import 'dart:ui';
66

7+
import 'package:flutter/foundation.dart';
78
import 'package:flutter/gestures.dart';
89
import 'package:flutter/material.dart';
910
import 'package:flutter/rendering.dart';
1011
import 'package:flutter/services.dart';
1112
import 'package:flutter_test/flutter_test.dart';
1213

14+
import '../foundation/leak_tracking.dart';
1315
import '../rendering/mock_canvas.dart';
1416
import '../widgets/semantics_tester.dart';
1517
import 'feedback_tester.dart';
@@ -649,38 +651,43 @@ void main() {
649651
});
650652

651653
testWidgets('Custom tooltip message textAlign', (WidgetTester tester) async {
652-
Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async {
653-
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
654-
await tester.pumpWidget(
655-
MaterialApp(
656-
home: Tooltip(
657-
key: tooltipKey,
658-
textAlign: textAlign,
659-
message: tooltipText,
660-
child: Container(
661-
width: 100.0,
662-
height: 100.0,
663-
color: Colors.green[500],
654+
await withFlutterLeakTracking(
655+
() async {
656+
Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async {
657+
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
658+
await tester.pumpWidget(
659+
MaterialApp(
660+
home: Tooltip(
661+
key: tooltipKey,
662+
textAlign: textAlign,
663+
message: tooltipText,
664+
child: Container(
665+
width: 100.0,
666+
height: 100.0,
667+
color: Colors.green[500],
668+
),
669+
),
664670
),
665-
),
666-
),
667-
);
668-
tooltipKey.currentState?.ensureTooltipVisible();
669-
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
670-
}
671-
672-
// Default value should be TextAlign.start
673-
await pumpTooltipWithTextAlign();
674-
TextAlign textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
675-
expect(textAlign, TextAlign.start);
676-
677-
await pumpTooltipWithTextAlign(textAlign: TextAlign.center);
678-
textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
679-
expect(textAlign, TextAlign.center);
680-
681-
await pumpTooltipWithTextAlign(textAlign: TextAlign.end);
682-
textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
683-
expect(textAlign, TextAlign.end);
671+
);
672+
tooltipKey.currentState?.ensureTooltipVisible();
673+
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
674+
}
675+
676+
// Default value should be TextAlign.start
677+
await pumpTooltipWithTextAlign();
678+
TextAlign textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
679+
expect(textAlign, TextAlign.start);
680+
681+
await pumpTooltipWithTextAlign(textAlign: TextAlign.center);
682+
textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
683+
expect(textAlign, TextAlign.center);
684+
685+
await pumpTooltipWithTextAlign(textAlign: TextAlign.end);
686+
textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
687+
expect(textAlign, TextAlign.end);
688+
},
689+
tester: tester,
690+
);
684691
});
685692

686693
testWidgets('Tooltip overlay respects ambient Directionality', (WidgetTester tester) async {
@@ -922,7 +929,7 @@ void main() {
922929
final Finder tooltip = find.byType(Tooltip);
923930
expect(find.text(tooltipText), findsNothing);
924931

925-
await testGestureTap(tester, tooltip);
932+
await _testGestureTap(tester, tooltip);
926933
expect(find.text(tooltipText), findsOneWidget);
927934

928935
// Tooltip is dismissed after showDuration expired
@@ -1697,7 +1704,7 @@ void main() {
16971704
expect(semanticEvents, unorderedEquals(<dynamic>[
16981705
<String, dynamic>{
16991706
'type': 'longPress',
1700-
'nodeId': findDebugSemantics(object).id,
1707+
'nodeId': _findDebugSemantics(object).id,
17011708
'data': <String, dynamic>{},
17021709
},
17031710
<String, dynamic>{
@@ -1790,7 +1797,7 @@ void main() {
17901797
final Finder tooltip = find.byType(Tooltip);
17911798
expect(find.text(tooltipText), findsNothing);
17921799

1793-
await testGestureTap(tester, tooltip);
1800+
await _testGestureTap(tester, tooltip);
17941801
expect(find.text(tooltipText), findsOneWidget);
17951802
});
17961803

@@ -1800,10 +1807,10 @@ void main() {
18001807
final Finder tooltip = find.byType(Tooltip);
18011808
expect(find.text(tooltipText), findsNothing);
18021809

1803-
await testGestureTap(tester, tooltip);
1810+
await _testGestureTap(tester, tooltip);
18041811
expect(find.text(tooltipText), findsNothing);
18051812

1806-
await testGestureLongPress(tester, tooltip);
1813+
await _testGestureLongPress(tester, tooltip);
18071814
expect(find.text(tooltipText), findsOneWidget);
18081815
});
18091816

@@ -1813,7 +1820,7 @@ void main() {
18131820
final Finder tooltip = find.byType(Tooltip);
18141821
expect(find.text(tooltipText), findsNothing);
18151822

1816-
await testGestureTap(tester, tooltip);
1823+
await _testGestureTap(tester, tooltip);
18171824
expect(find.text(tooltipText), findsNothing);
18181825
});
18191826

@@ -1823,10 +1830,10 @@ void main() {
18231830
final Finder tooltip = find.byType(Tooltip);
18241831
expect(find.text(tooltipText), findsNothing);
18251832

1826-
await testGestureTap(tester, tooltip);
1833+
await _testGestureTap(tester, tooltip);
18271834
expect(find.text(tooltipText), findsNothing);
18281835

1829-
await testGestureLongPress(tester, tooltip);
1836+
await _testGestureLongPress(tester, tooltip);
18301837
expect(find.text(tooltipText), findsNothing);
18311838
});
18321839

@@ -1836,13 +1843,13 @@ void main() {
18361843

18371844
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress, onTriggered: onTriggered);
18381845
Finder tooltip = find.byType(Tooltip);
1839-
await testGestureLongPress(tester, tooltip);
1846+
await _testGestureLongPress(tester, tooltip);
18401847
expect(onTriggeredCalled, true);
18411848

18421849
onTriggeredCalled = false;
18431850
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, onTriggered: onTriggered);
18441851
tooltip = find.byType(Tooltip);
1845-
await testGestureTap(tester, tooltip);
1852+
await _testGestureTap(tester, tooltip);
18461853
expect(onTriggeredCalled, true);
18471854
});
18481855

@@ -1925,22 +1932,22 @@ Future<void> setWidgetForTooltipMode(
19251932
);
19261933
}
19271934

1928-
Future<void> testGestureLongPress(WidgetTester tester, Finder tooltip) async {
1935+
Future<void> _testGestureLongPress(WidgetTester tester, Finder tooltip) async {
19291936
final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(tooltip));
19301937
await tester.pump();
19311938
await tester.pump(kLongPressTimeout);
19321939
await gestureLongPress.up();
19331940
await tester.pump();
19341941
}
19351942

1936-
Future<void> testGestureTap(WidgetTester tester, Finder tooltip) async {
1943+
Future<void> _testGestureTap(WidgetTester tester, Finder tooltip) async {
19371944
await tester.tap(tooltip);
19381945
await tester.pump(const Duration(milliseconds: 10));
19391946
}
19401947

1941-
SemanticsNode findDebugSemantics(RenderObject object) {
1948+
SemanticsNode _findDebugSemantics(RenderObject object) {
19421949
if (object.debugSemantics != null) {
19431950
return object.debugSemantics!;
19441951
}
1945-
return findDebugSemantics(object.parent! as RenderObject);
1952+
return _findDebugSemantics(object.parent! as RenderObject);
19461953
}

0 commit comments

Comments
 (0)