From 7d0c9b7211d5be55b9566f582cd6b972c045af25 Mon Sep 17 00:00:00 2001 From: Kostia Sokolovskyi Date: Wed, 13 Sep 2023 02:43:34 +0200 Subject: [PATCH] Cover more test/widgets tests with leak tracking (#134387) --- packages/flutter/test/widgets/app_test.dart | 8 +- .../flutter/test/widgets/draggable_test.dart | 132 +- .../flutter/test/widgets/drawer_test.dart | 19 +- .../widgets/dual_transition_builder_test.dart | 15 +- .../widgets/editable_text_cursor_test.dart | 210 ++- .../widgets/editable_text_shortcuts_test.dart | 187 +-- .../editable_text_show_on_screen_test.dart | 136 +- .../test/widgets/editable_text_test.dart | 1425 +++++++++-------- .../test/widgets/ensure_visible_test.dart | 35 +- .../widgets/error_widget_builder_test.dart | 5 +- .../test/widgets/fade_in_image_test.dart | 53 +- .../test/widgets/fade_transition_test.dart | 3 +- .../flutter/test/widgets/fitted_box_test.dart | 29 +- packages/flutter/test/widgets/flex_test.dart | 11 +- packages/flutter/test/widgets/flow_test.dart | 11 +- 15 files changed, 1209 insertions(+), 1070 deletions(-) diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart index d6274ce11e19..38c67439767b 100644 --- a/packages/flutter/test/widgets/app_test.dart +++ b/packages/flutter/test/widgets/app_test.dart @@ -306,7 +306,7 @@ void main() { expect(find.text('popped'), findsOneWidget); }, leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(someone): remove after fixing + // TODO(ksokolovskyi): remove after fixing // https://github.com/flutter/flutter/issues/134205 notDisposedAllowList: {'_RestorableRouteInformation': 1}, )); @@ -338,7 +338,7 @@ void main() { expect(find.text('popped'), findsOneWidget); }, leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(someone): remove after fixing + // TODO(ksokolovskyi): remove after fixing // https://github.com/flutter/flutter/issues/134205 notDisposedAllowList: {'_RestorableRouteInformation': 1}, )); @@ -434,7 +434,7 @@ void main() { expect(find.text('popped'), findsOneWidget); }, leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(someone): remove after fixing + // TODO(ksokolovskyi): remove after fixing // https://github.com/flutter/flutter/issues/134205 notDisposedAllowList: {'_RestorableRouteInformation': 1}, )); @@ -455,7 +455,7 @@ void main() { expect(find.text('/'), findsOneWidget); }, leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(someone): remove after fixing + // TODO(ksokolovskyi): remove after fixing // https://github.com/flutter/flutter/issues/134205 notDisposedAllowList: {'_RestorableRouteInformation': 1}, )); diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index ac91266e41fe..f03125c352bd 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -14,11 +14,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'semantics_tester.dart'; void main() { - testWidgets('Drag and drop - control test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - control test', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; int dragStartedCount = 0; @@ -93,7 +94,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/76825 - testWidgets('Drag and drop - onLeave callback fires correctly with generic parameter', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onLeave callback fires correctly with generic parameter', (WidgetTester tester) async { final Map leftBehind = { 'Target 1': 0, 'Target 2': 0, @@ -168,7 +169,7 @@ void main() { expect(leftBehind['Target 2'], equals(1)); }); - testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async { final Map leftBehind = { 'Target 1': 0, 'Target 2': 0, @@ -244,7 +245,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/76825 - testWidgets('Drag and drop - onMove callback fires correctly with generic parameter', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onMove callback fires correctly with generic parameter', (WidgetTester tester) async { final Map targetMoveCount = { 'Target 1': 0, 'Target 2': 0, @@ -317,7 +318,7 @@ void main() { expect(targetMoveCount['Target 2'], equals(1)); }); - testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async { final Map targetMoveCount = { 'Target 1': 0, 'Target 2': 0, @@ -394,7 +395,7 @@ void main() { expect(targetMoveCount['Target 2'], equals(1)); }); - testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - dragging over button', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; @@ -487,7 +488,7 @@ void main() { events.clear(); }); - testWidgets('Drag and drop - tapping button', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - tapping button', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; @@ -544,7 +545,7 @@ void main() { events.clear(); }); - testWidgets('Drag and drop - long press draggable, short press', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - long press draggable, short press', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; @@ -593,7 +594,7 @@ void main() { expect(events, isEmpty); }); - testWidgets('Drag and drop - long press draggable, long press', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - long press draggable, long press', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; @@ -644,7 +645,7 @@ void main() { expect(events, equals(['drop', 'details'])); }); - testWidgets('Drag and drop - horizontal and vertical draggables in vertical block', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - horizontal and vertical draggables in vertical block', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation, thirdLocation; @@ -754,7 +755,7 @@ void main() { events.clear(); }); - testWidgets('Drag and drop - horizontal and vertical draggables in horizontal block', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - horizontal and vertical draggables in horizontal block', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation, thirdLocation; @@ -913,7 +914,7 @@ void main() { ), ); } - testWidgets('Null axis draggable moves along all axes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Null axis draggable moves along all axes', (WidgetTester tester) async { await tester.pumpWidget(build()); final Offset firstLocation = tester.getTopLeft(find.text('N')); final Offset secondLocation = firstLocation + const Offset(300.0, 300.0); @@ -928,7 +929,7 @@ void main() { expect(tester.getTopLeft(find.text('N')), thirdLocation); }); - testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Horizontal axis draggable moves horizontally', (WidgetTester tester) async { await tester.pumpWidget(build()); final Offset firstLocation = tester.getTopLeft(find.text('H')); final Offset secondLocation = firstLocation + const Offset(300.0, 0.0); @@ -943,7 +944,7 @@ void main() { expect(tester.getTopLeft(find.text('H')), thirdLocation); }); - testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Horizontal axis draggable does not move vertically', (WidgetTester tester) async { await tester.pumpWidget(build()); final Offset firstLocation = tester.getTopLeft(find.text('H')); final Offset secondDragLocation = firstLocation + const Offset(300.0, 200.0); @@ -961,7 +962,7 @@ void main() { expect(tester.getTopLeft(find.text('H')), thirdWidgetLocation); }); - testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Vertical axis draggable moves vertically', (WidgetTester tester) async { await tester.pumpWidget(build()); final Offset firstLocation = tester.getTopLeft(find.text('V')); final Offset secondLocation = firstLocation + const Offset(0.0, 300.0); @@ -976,7 +977,7 @@ void main() { expect(tester.getTopLeft(find.text('V')), thirdLocation); }); - testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Vertical axis draggable does not move horizontally', (WidgetTester tester) async { await tester.pumpWidget(build()); final Offset firstLocation = tester.getTopLeft(find.text('V')); final Offset secondDragLocation = firstLocation + const Offset(200.0, 300.0); @@ -1042,7 +1043,7 @@ void main() { ); } - testWidgets('Null axis onDragUpdate called only if draggable moves in any direction', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Null axis onDragUpdate called only if draggable moves in any direction', (WidgetTester tester) async { await tester.pumpWidget(build()); expect(updated, 0); @@ -1077,7 +1078,7 @@ void main() { expect(dragDelta.dy, 10); }); - testWidgets('Vertical axis onDragUpdate only called if draggable moves vertical', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Vertical axis onDragUpdate only called if draggable moves vertical', (WidgetTester tester) async { await tester.pumpWidget(build()); expect(updated, 0); @@ -1112,7 +1113,7 @@ void main() { expect(dragDelta.dy, 10); }); - testWidgets('Horizontal axis onDragUpdate only called if draggable moves horizontal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Horizontal axis onDragUpdate only called if draggable moves horizontal', (WidgetTester tester) async { await tester.pumpWidget(build()); expect(updated, 0); @@ -1148,7 +1149,7 @@ void main() { }); }); - testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDraggableCanceledCalled = false; @@ -1216,7 +1217,7 @@ void main() { expect(onDraggableCanceledCalled, isFalse); }); - testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDraggableCanceledCalled = false; @@ -1293,7 +1294,7 @@ void main() { expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy))); }); - testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with details', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with details', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDraggableCanceledCalled = false; @@ -1370,7 +1371,7 @@ void main() { expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy))); }); - testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDraggableCanceledCalled = false; @@ -1422,7 +1423,7 @@ void main() { expect(onDraggableCanceledOffset, equals(Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0))); }); - testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragEnd not called if dropped on non-accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragEndCalled = false; @@ -1498,7 +1499,7 @@ void main() { ); }); - testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target with details', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragEnd not called if dropped on non-accepting target with details', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragEndCalled = false; @@ -1574,7 +1575,7 @@ void main() { ); }); - testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Column( children: [ @@ -1628,7 +1629,7 @@ void main() { }); - testWidgets('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async { int numberOfTimesOnDraggableCanceledCalled = 0; await tester.pumpWidget(MaterialApp( home: Column( @@ -1710,7 +1711,7 @@ void main() { expect(find.text('Rejected'), findsNothing); }); - testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragCompletedCalled = false; @@ -1781,7 +1782,7 @@ void main() { expect(onDragCompletedCalled, isFalse); }); - testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target with details', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragCompleted not called if dropped on non-accepting target with details', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragCompletedCalled = false; @@ -1852,7 +1853,7 @@ void main() { expect(onDragCompletedCalled, isFalse); }); - testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragEndCalled = false; @@ -1928,7 +1929,7 @@ void main() { expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset)); }); - testWidgets('DragTarget does not call onDragEnd when remove from the tree', (WidgetTester tester) async { + testWidgetsWithLeakTracking('DragTarget does not call onDragEnd when remove from the tree', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; int timesOnDragEndCalled = 0; @@ -1994,7 +1995,7 @@ void main() { await tester.pump(); }); - testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragCompletedCalled = false; @@ -2062,7 +2063,7 @@ void main() { expect(onDragCompletedCalled, isTrue); }); - testWidgets('Drag and drop - allow pass through of unaccepted data test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - allow pass through of unaccepted data test', (WidgetTester tester) async { final List acceptedInts = []; final List> acceptedIntsDetails = >[]; final List acceptedDoubles = []; @@ -2196,7 +2197,7 @@ void main() { expect(find.text('DoubleDragging'), findsNothing); }); - testWidgets('Drag and drop - allow pass through of unaccepted data twice test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - allow pass through of unaccepted data twice test', (WidgetTester tester) async { final List acceptedDragTargetDatas = []; final List> acceptedDragTargetDataDetails = >[]; final List acceptedExtendedDragTargetDatas = []; @@ -2264,7 +2265,7 @@ void main() { } }); - testWidgets('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; @@ -2391,14 +2392,17 @@ void main() { expect(find.text('Target'), findsOneWidget); }); - testWidgets('Draggable disposes recognizer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Draggable disposes recognizer', (WidgetTester tester) async { + late final OverlayEntry entry; + addTearDown(() => entry..remove()..dispose()); + bool didTap = false; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + entry = OverlayEntry( builder: (BuildContext context) => GestureDetector( onTap: () { didTap = true; @@ -2430,7 +2434,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/6128. - testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Draggable plays nice with onTap', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -2463,7 +2467,7 @@ void main() { await secondGesture.up(); }); - testWidgets('DragTarget does not set state when remove from the tree', (WidgetTester tester) async { + testWidgetsWithLeakTracking('DragTarget does not set state when remove from the tree', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation; @@ -2525,7 +2529,7 @@ void main() { await tester.pump(); }); - testWidgets('Drag and drop - remove draggable', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - remove draggable', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; @@ -2605,7 +2609,7 @@ void main() { expect(find.text('Target'), findsOneWidget); }); - testWidgets('Tap above long-press draggable works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Tap above long-press draggable works', (WidgetTester tester) async { final List events = []; await tester.pumpWidget(MaterialApp( @@ -2629,7 +2633,7 @@ void main() { expect(events, equals(['tap'])); }); - testWidgets('long-press draggable calls onDragEnd called if dropped on accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long-press draggable calls onDragEnd called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragEndCalled = false; @@ -2716,7 +2720,7 @@ void main() { expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset)); }); - testWidgets('long-press draggable calls onDragCompleted called if dropped on accepting target', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long-press draggable calls onDragCompleted called if dropped on accepting target', (WidgetTester tester) async { final List accepted = []; final List> acceptedDetails = >[]; bool onDragCompletedCalled = false; @@ -2792,7 +2796,7 @@ void main() { expect(onDragCompletedCalled, isTrue); }); - testWidgets('long-press draggable calls onDragStartedCalled after long press', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long-press draggable calls onDragStartedCalled after long press', (WidgetTester tester) async { bool onDragStartedCalled = false; await tester.pumpWidget(MaterialApp( @@ -2825,7 +2829,7 @@ void main() { expect(onDragStartedCalled, isTrue); }); - testWidgets('Custom long press delay for LongPressDraggable', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Custom long press delay for LongPressDraggable', (WidgetTester tester) async { bool onDragStartedCalled = false; await tester.pumpWidget(MaterialApp( home: LongPressDraggable( @@ -2859,7 +2863,7 @@ void main() { expect(onDragStartedCalled, isTrue); }); - testWidgets('Default long press delay for LongPressDraggable', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Default long press delay for LongPressDraggable', (WidgetTester tester) async { bool onDragStartedCalled = false; await tester.pumpWidget(MaterialApp( home: LongPressDraggable( @@ -2892,23 +2896,23 @@ void main() { expect(onDragStartedCalled, isTrue); }); - testWidgets('long-press draggable calls Haptic Feedback onStart', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long-press draggable calls Haptic Feedback onStart', (WidgetTester tester) async { await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: true, expectedHapticFeedbackCount: 1); }); - testWidgets('long-press draggable can disable Haptic Feedback', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long-press draggable can disable Haptic Feedback', (WidgetTester tester) async { await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: false, expectedHapticFeedbackCount: 0); }); - testWidgets('Drag feedback with child anchor positions correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag feedback with child anchor positions correctly', (WidgetTester tester) async { await _testChildAnchorFeedbackPosition(tester: tester); }); - testWidgets('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async { await _testChildAnchorFeedbackPosition(tester: tester, left: 100.0, top: 100.0); }); - testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async { final GlobalKey rootNavigatorKey = GlobalKey(); final GlobalKey childNavigatorKey = GlobalKey(); // Create a [MaterialApp], with a nested [Navigator], which has the @@ -2976,7 +2980,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/72483 - testWidgets('Drag and drop - DragTarget can accept Draggable data', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - DragTarget can accept Draggable data', (WidgetTester tester) async { final List accepted = []; await tester.pumpWidget(MaterialApp( home: Column( @@ -3012,7 +3016,7 @@ void main() { expect(accepted, equals([1])); }); - testWidgets('Drag and drop - DragTarget can accept Draggable data when runtime type is int', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - DragTarget can accept Draggable data when runtime type is int', (WidgetTester tester) async { final List accepted = []; await tester.pumpWidget(MaterialApp( home: Column( @@ -3048,7 +3052,7 @@ void main() { expect(accepted, equals([1])); }); - testWidgets('Drag and drop - DragTarget should not accept Draggable data when runtime type null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - DragTarget should not accept Draggable data when runtime type null', (WidgetTester tester) async { final List accepted = []; bool isReceiveNullDataForCheck = false; await tester.pumpWidget(MaterialApp( @@ -3091,7 +3095,7 @@ void main() { expect(isReceiveNullDataForCheck, true); }); - testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop can contribute semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(MaterialApp( home: ListView( @@ -3257,7 +3261,7 @@ void main() { semantics.dispose(); }); - testWidgets('Drag and drop - when a dragAnchorStrategy is provided it gets called', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drag and drop - when a dragAnchorStrategy is provided it gets called', (WidgetTester tester) async { bool dragAnchorStrategyCalled = false; await tester.pumpWidget(MaterialApp( @@ -3281,7 +3285,7 @@ void main() { expect(dragAnchorStrategyCalled, true); }); - testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('configurable Draggable hit test behavior', (WidgetTester tester) async { const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild; await tester.pumpWidget( @@ -3301,7 +3305,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/92083 - testWidgets('feedback respect the MouseRegion cursor configure', (WidgetTester tester) async { + testWidgetsWithLeakTracking('feedback respect the MouseRegion cursor configure', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Column( @@ -3329,7 +3333,7 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grabbing); }); - testWidgets('configurable feedback ignore pointer behavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('configurable feedback ignore pointer behavior', (WidgetTester tester) async { bool onTap = false; await tester.pumpWidget( MaterialApp( @@ -3358,7 +3362,7 @@ void main() { expect(onTap, true); }); - testWidgets('configurable feedback ignore pointer behavior - LongPressDraggable', (WidgetTester tester) async { + testWidgetsWithLeakTracking('configurable feedback ignore pointer behavior - LongPressDraggable', (WidgetTester tester) async { bool onTap = false; await tester.pumpWidget( MaterialApp( @@ -3389,7 +3393,7 @@ void main() { expect(onTap, true); }); - testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('configurable DragTarget hit test behavior', (WidgetTester tester) async { const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild; await tester.pumpWidget( @@ -3410,7 +3414,7 @@ void main() { expect(tester.widget(find.byType(MetaData)).behavior, hitTestBehavior); }); - testWidgets('LongPressDraggable.dragAnchorStrategy', (WidgetTester tester) async { + testWidgetsWithLeakTracking('LongPressDraggable.dragAnchorStrategy', (WidgetTester tester) async { const Widget widget1 = Placeholder(key: ValueKey(1)); const Widget widget2 = Placeholder(key: ValueKey(2)); Offset dummyStrategy(Draggable draggable, BuildContext context, Offset position) => Offset.zero; @@ -3420,7 +3424,7 @@ void main() { expect(LongPressDraggable(feedback: widget2, dragAnchorStrategy: dummyStrategy, child: widget1).dragAnchorStrategy, dummyStrategy); }); - testWidgets('Test allowedButtonsFilter', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Test allowedButtonsFilter', (WidgetTester tester) async { Widget build(bool Function(int buttons)? allowedButtonsFilter) { return MaterialApp( home: Draggable( @@ -3462,7 +3466,7 @@ void main() { await gesture3.up(); }); - testWidgets('throws error when both onWillAccept and onWillAcceptWithDetails are provided', (WidgetTester tester) async { + testWidgetsWithLeakTracking('throws error when both onWillAccept and onWillAcceptWithDetails are provided', (WidgetTester tester) async { expect(() => DragTarget( builder: (BuildContext context, List data, List rejects) { return const SizedBox(height: 100.0, child: Text('Target')); diff --git a/packages/flutter/test/widgets/drawer_test.dart b/packages/flutter/test/widgets/drawer_test.dart index cb3aba7fd828..d1a7ce8f3b22 100644 --- a/packages/flutter/test/widgets/drawer_test.dart +++ b/packages/flutter/test/widgets/drawer_test.dart @@ -8,12 +8,13 @@ import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'semantics_tester.dart'; void main() { - testWidgets('Drawer control test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer control test', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); late BuildContext savedContext; await tester.pumpWidget( @@ -44,7 +45,7 @@ void main() { expect(find.text('drawer'), findsNothing); }); - testWidgets('Drawer tap test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer tap test', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -76,7 +77,7 @@ void main() { expect(find.text('drawer'), findsNothing); }); - testWidgets('Drawer hover test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer hover test', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); final List logs = []; final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); @@ -146,7 +147,7 @@ void main() { logs.clear(); }); - testWidgets('Drawer drag cancel resume (LTR)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer drag cancel resume (LTR)', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -197,7 +198,7 @@ void main() { await gesture.up(); }); - testWidgets('Drawer drag cancel resume (RTL)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer drag cancel resume (RTL)', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -251,7 +252,7 @@ void main() { await gesture.up(); }); - testWidgets('Drawer navigator back button', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer navigator back button', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); bool buttonPressed = false; @@ -299,7 +300,7 @@ void main() { expect(buttonPressed, equals(true)); }); - testWidgets('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey scaffoldKey = GlobalKey(); @@ -326,7 +327,7 @@ void main() { semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey scaffoldKey = GlobalKey(); @@ -354,7 +355,7 @@ void main() { semantics.dispose(); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); - testWidgets('Drawer contains route semantics flags', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Drawer contains route semantics flags', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey scaffoldKey = GlobalKey(); diff --git a/packages/flutter/test/widgets/dual_transition_builder_test.dart b/packages/flutter/test/widgets/dual_transition_builder_test.dart index 4ffca538d978..531aeead57ea 100644 --- a/packages/flutter/test/widgets/dual_transition_builder_test.dart +++ b/packages/flutter/test/widgets/dual_transition_builder_test.dart @@ -4,13 +4,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('runs animations', (WidgetTester tester) async { + testWidgetsWithLeakTracking('runs animations', (WidgetTester tester) async { final AnimationController controller = AnimationController( vsync: const TestVSync(), duration: const Duration(milliseconds: 300), ); + addTearDown(controller.dispose); await tester.pumpWidget(Center( child: DualTransitionBuilder( @@ -74,11 +76,12 @@ void main() { expect(_getOpacity(tester), 1.0); }); - testWidgets('keeps state', (WidgetTester tester) async { + testWidgetsWithLeakTracking('keeps state', (WidgetTester tester) async { final AnimationController controller = AnimationController( vsync: const TestVSync(), duration: const Duration(milliseconds: 300), ); + addTearDown(controller.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, @@ -138,11 +141,13 @@ void main() { expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); }); - testWidgets('does not jump when interrupted - forward', (WidgetTester tester) async { + testWidgetsWithLeakTracking('does not jump when interrupted - forward', (WidgetTester tester) async { final AnimationController controller = AnimationController( vsync: const TestVSync(), duration: const Duration(milliseconds: 300), ); + addTearDown(controller.dispose); + await tester.pumpWidget(Center( child: DualTransitionBuilder( animation: controller, @@ -202,12 +207,14 @@ void main() { expect(_getOpacity(tester), 1.0); }); - testWidgets('does not jump when interrupted - reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('does not jump when interrupted - reverse', (WidgetTester tester) async { final AnimationController controller = AnimationController( value: 1.0, vsync: const TestVSync(), duration: const Duration(milliseconds: 300), ); + addTearDown(controller.dispose); + await tester.pumpWidget(Center( child: DualTransitionBuilder( animation: controller, diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index e52e7bcf91bf..ab1d8137b721 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -15,23 +15,34 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'editable_text_utils.dart'; -final TextEditingController controller = TextEditingController(); -final FocusNode focusNode = FocusNode(); -final FocusScopeNode focusScopeNode = FocusScopeNode(); const TextStyle textStyle = TextStyle(); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); void main() { + late TextEditingController controller; + late FocusNode focusNode; + late FocusScopeNode focusScopeNode; + setUp(() async { // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); + controller = TextEditingController(); + focusNode = FocusNode(); + focusScopeNode = FocusScopeNode(); + }); + + tearDown(() { + controller.dispose(); + focusNode.dispose(); + focusScopeNode.dispose(); }); - testWidgets('cursor has expected width, height, and radius', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cursor has expected width, height, and radius', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -57,7 +68,7 @@ void main() { expect(editableText.cursorRadius!.x, 2.0); }); - testWidgets('cursor layout has correct width', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cursor layout has correct width', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; final GlobalKey editableTextKey = GlobalKey(); @@ -68,8 +79,8 @@ void main() { child: EditableText( backgroundCursorColor: Colors.grey, key: editableTextKey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -113,7 +124,7 @@ void main() { EditableText.debugDeterministicCursor = false; }); - testWidgets('cursor layout has correct radius', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cursor layout has correct radius', (WidgetTester tester) async { final GlobalKey editableTextKey = GlobalKey(); late String changedValue; @@ -123,8 +134,8 @@ void main() { child: EditableText( backgroundCursorColor: Colors.grey, key: editableTextKey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -168,7 +179,7 @@ void main() { ); }); - testWidgets('Cursor animates on iOS', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor animates on iOS', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Material( @@ -219,7 +230,7 @@ void main() { await verifyKeyFrame(opacity: 1.0, at: 1000000); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); - testWidgets('Cursor does not animate on non-iOS platforms', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor does not animate on non-iOS platforms', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Material(child: TextField(maxLines: 3)), @@ -238,7 +249,7 @@ void main() { } }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('Cursor does not animate on Android', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor does not animate on Android', (WidgetTester tester) async { final Color defaultCursorColor = Color(ThemeData.fallback().colorScheme.primary.value); const Widget widget = MaterialApp( home: Material( @@ -276,9 +287,14 @@ void main() { await tester.pump(const Duration(milliseconds: 500)); expect(renderEditable.cursorColor!.alpha, 0); expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); - }); - - testWidgets('Cursor does not animates when debugDeterministicCursor is set', (WidgetTester tester) async { + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 5}, + )); + + testWidgetsWithLeakTracking('Cursor does not animates when debugDeterministicCursor is set', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; final Color defaultCursorColor = Color(ThemeData.fallback().colorScheme.primary.value); const Widget widget = MaterialApp( @@ -314,9 +330,15 @@ void main() { expect(renderEditable, paints..rrect(color: defaultCursorColor)); EditableText.debugDeterministicCursor = false; - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - - testWidgets('Cursor does not animate on Android when debugDeterministicCursor is set', (WidgetTester tester) async { + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 6}, + )); + + testWidgetsWithLeakTracking('Cursor does not animate on Android when debugDeterministicCursor is set', (WidgetTester tester) async { final Color defaultCursorColor = Color(ThemeData.fallback().colorScheme.primary.value); EditableText.debugDeterministicCursor = true; const Widget widget = MaterialApp( @@ -353,17 +375,23 @@ void main() { expect(renderEditable, paints..rect(color: defaultCursorColor)); EditableText.debugDeterministicCursor = false; - }); - - testWidgets('Cursor animation restarts when it is moved using keys on desktop', (WidgetTester tester) async { + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 4}, + )); + + testWidgetsWithLeakTracking('Cursor animation restarts when it is moved using keys on desktop', (WidgetTester tester) async { debugDefaultTargetPlatformOverride = TargetPlatform.macOS; const String testText = 'Some text long enough to move the cursor around'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; + final Widget widget = MaterialApp( home: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: const TextStyle(fontSize: 20.0), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -430,9 +458,15 @@ void main() { expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); debugDefaultTargetPlatformOverride = null; - }, variant: KeySimulatorTransitModeVariant.all()); - - testWidgets('Cursor does not show when showCursor set to false', (WidgetTester tester) async { + }, + variant: KeySimulatorTransitModeVariant.all(), + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 18}, + )); + + testWidgetsWithLeakTracking('Cursor does not show when showCursor set to false', (WidgetTester tester) async { const Widget widget = MaterialApp( home: Material( child: TextField( @@ -458,11 +492,15 @@ void main() { await tester.pump(const Duration(milliseconds: 200)); expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); - }); - - testWidgets('Cursor does not show when not focused', (WidgetTester tester) async { + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 3}, + )); + + testWidgetsWithLeakTracking('Cursor does not show when not focused', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/106512 . - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MaterialApp( home: Material( @@ -489,9 +527,14 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(renderEditable, isNot(paintsExactlyCountTimes(#drawRect, 0))); - }); - - testWidgets('Cursor radius is 2.0', (WidgetTester tester) async { + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 2}, + )); + + testWidgetsWithLeakTracking('Cursor radius is 2.0', (WidgetTester tester) async { const Widget widget = MaterialApp( home: Material( child: TextField( @@ -507,10 +550,9 @@ void main() { expect(renderEditable.cursorRadius, const Radius.circular(2.0)); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('Cursor gets placed correctly after going out of bounds', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor gets placed correctly after going out of bounds', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MediaQuery( @@ -602,10 +644,9 @@ void main() { expect(controller.selection.baseOffset, 10); }); - testWidgets('Updating the floating cursor correctly moves the cursor', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Updating the floating cursor correctly moves the cursor', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MediaQuery( @@ -659,10 +700,9 @@ void main() { expect(controller.selection.baseOffset, 10); }); - testWidgets('Updating the floating cursor can end without update', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Updating the floating cursor can end without update', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MediaQuery( @@ -703,10 +743,9 @@ void main() { expect(tester.takeException(), null); }); - testWidgets("Drag the floating cursor, it won't blink.", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Drag the floating cursor, it won't blink.", (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MediaQuery( @@ -770,7 +809,7 @@ void main() { await checkCursorBlinking(); }); - testWidgets('Turning showCursor off stops the cursor', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Turning showCursor off stops the cursor', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/108187. final bool debugDeterministicCursor = EditableText.debugDeterministicCursor; // This doesn't really matter. @@ -819,10 +858,9 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/pull/30475. - testWidgets('Trying to select with the floating cursor does not crash', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Trying to select with the floating cursor does not crash', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MediaQuery( @@ -885,12 +923,10 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('autofocus sets cursor to the end of text', (WidgetTester tester) async { + testWidgetsWithLeakTracking('autofocus sets cursor to the end of text', (WidgetTester tester) async { const String text = 'hello world'; - final FocusScopeNode focusScopeNode = FocusScopeNode(); - final FocusNode focusNode = FocusNode(); - controller.text = text; + await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -917,12 +953,10 @@ void main() { expect(controller.selection.baseOffset, text.length); }); - testWidgets('Floating cursor is painted', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('Floating cursor is painted', (WidgetTester tester) async { const TextStyle textStyle = TextStyle(); const String text = 'hello world this is fun and cool and awesome!'; controller.text = text; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MaterialApp( @@ -995,9 +1029,15 @@ void main() { editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End)); await tester.pumpAndSettle(); debugDefaultTargetPlatformOverride = null; - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - - testWidgets('cursor layout', (WidgetTester tester) async { + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 4}, + )); + + testWidgetsWithLeakTracking('cursor layout', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; final GlobalKey editableTextKey = GlobalKey(); @@ -1011,8 +1051,8 @@ void main() { EditableText( backgroundCursorColor: Colors.grey, key: editableTextKey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018(platform: TargetPlatform.iOS).black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -1058,7 +1098,7 @@ void main() { EditableText.debugDeterministicCursor = false; }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('cursor layout has correct height', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cursor layout has correct height', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; final GlobalKey editableTextKey = GlobalKey(); @@ -1072,8 +1112,8 @@ void main() { EditableText( backgroundCursorColor: Colors.grey, key: editableTextKey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018(platform: TargetPlatform.iOS).black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -1120,7 +1160,7 @@ void main() { EditableText.debugDeterministicCursor = false; }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('password briefly does not show last character when disabled by system', (WidgetTester tester) async { + testWidgetsWithLeakTracking('password briefly does not show last character when disabled by system', (WidgetTester tester) async { final bool debugDeterministicCursor = EditableText.debugDeterministicCursor; EditableText.debugDeterministicCursor = false; addTearDown(() { @@ -1156,23 +1196,23 @@ void main() { expect((findRenderEditable(tester).text! as TextSpan).text, '•••'); }); - testWidgets('getLocalRectForCaret with empty text', (WidgetTester tester) async { + testWidgetsWithLeakTracking('getLocalRectForCaret with empty text', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; addTearDown(() { EditableText.debugDeterministicCursor = false; }); const String text = '12'; - final TextEditingController controller = TextEditingController.fromValue( const TextEditingValue( text: text, selection: TextSelection.collapsed(offset: text.length), ), ); + addTearDown(controller.dispose); final Widget widget = EditableText( autofocus: true, backgroundCursorColor: Colors.grey, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: const TextStyle(fontSize: 20), textAlign: TextAlign.center, keyboardType: TextInputType.text, @@ -1201,21 +1241,23 @@ void main() { expect(controller.text, isEmpty); }); - testWidgets('Caret center space test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Caret center space test', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; addTearDown(() { EditableText.debugDeterministicCursor = false; }); final String text = 'test${' ' * 1000}'; + final TextEditingController controller = TextEditingController.fromValue( + TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length, affinity: TextAffinity.upstream), + ), + ); + addTearDown(controller.dispose); final Widget widget = EditableText( autofocus: true, backgroundCursorColor: Colors.grey, - controller: TextEditingController.fromValue( - TextEditingValue( - text: text, - selection: TextSelection.collapsed(offset: text.length, affinity: TextAffinity.upstream), - ), - ), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), textAlign: TextAlign.center, keyboardType: TextInputType.text, @@ -1243,25 +1285,31 @@ void main() { renderEditable, paints..rect(color: cursorColor, rect: caretRect), ); - }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 - - testWidgets('getLocalRectForCaret reports the real caret Rect', (WidgetTester tester) async { + }, + skip: isBrowser && !isCanvasKit, // https://github.com/flutter/flutter/issues/56308 + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 1}, + )); + + testWidgetsWithLeakTracking('getLocalRectForCaret reports the real caret Rect', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; addTearDown(() { EditableText.debugDeterministicCursor = false; }); final String text = 'test${' ' * 50}\n' '2nd line\n' '\n'; - final TextEditingController controller = TextEditingController.fromValue(TextEditingValue( text: text, selection: const TextSelection.collapsed(offset: 0), )); + addTearDown(controller.dispose); final Widget widget = EditableText( autofocus: true, backgroundCursorColor: Colors.grey, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: const TextStyle(fontSize: 20), textAlign: TextAlign.center, keyboardType: TextInputType.text, @@ -1287,5 +1335,11 @@ void main() { paints..rect(color: cursorColor, rect: localRect.shift(editableTextRect.topLeft)), ); } - }, variant: TargetPlatformVariant.all()); + }, + variant: TargetPlatformVariant.all(), + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 792}, + )); } diff --git a/packages/flutter/test/widgets/editable_text_shortcuts_test.dart b/packages/flutter/test/widgets/editable_text_shortcuts_test.dart index b6efb36c221d..449fe0029eb5 100644 --- a/packages/flutter/test/widgets/editable_text_shortcuts_test.dart +++ b/packages/flutter/test/widgets/editable_text_shortcuts_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'clipboard_utils.dart'; import 'keyboard_utils.dart'; @@ -97,7 +98,7 @@ void main() { group('backspace', () { const LogicalKeyboardKey trigger = LogicalKeyboardKey.backspace; - testWidgets('backspace', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace', (WidgetTester tester) async { controller.text = testText; // Move the selection to the beginning of the 2nd line (after the newline // character). @@ -122,7 +123,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('backspace readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 20, @@ -140,7 +141,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('backspace at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -163,7 +164,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('backspace at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -187,7 +188,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('backspace inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -208,7 +209,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('backspace at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('backspace at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -233,7 +234,7 @@ void main() { group('delete: ', () { const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete; - testWidgets('delete', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete', (WidgetTester tester) async { controller.text = testText; // Move the selection to the beginning of the 2nd line (after the newline // character). @@ -259,7 +260,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('delete readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 20, @@ -277,7 +278,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('delete at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -300,7 +301,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('delete at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -324,7 +325,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('delete inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -345,7 +346,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('delete at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delete at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -371,7 +372,7 @@ void main() { // This shares the same logic as backspace. const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete; - testWidgets('inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection( baseOffset: 9, @@ -392,7 +393,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('at the boundaries of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at the boundaries of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection( baseOffset: 8, @@ -413,7 +414,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('cross-cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cross-cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection( baseOffset: 1, @@ -434,7 +435,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('cross-cluster obscured text', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cross-cluster obscured text', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection( baseOffset: 1, @@ -464,7 +465,7 @@ void main() { return SingleActivator(trigger, control: !isApple, alt: isApple); } - testWidgets('WordModifier-backspace', (WidgetTester tester) async { + testWidgetsWithLeakTracking('WordModifier-backspace', (WidgetTester tester) async { controller.text = testText; // Place the caret before "people". controller.selection = const TextSelection.collapsed( @@ -489,7 +490,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 29, @@ -507,7 +508,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -530,7 +531,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -554,7 +555,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -575,7 +576,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -604,7 +605,7 @@ void main() { return SingleActivator(trigger, control: !isApple, alt: isApple); } - testWidgets('WordModifier-delete', (WidgetTester tester) async { + testWidgetsWithLeakTracking('WordModifier-delete', (WidgetTester tester) async { controller.text = testText; // Place the caret after "all". controller.selection = const TextSelection.collapsed( @@ -629,7 +630,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 23, @@ -647,7 +648,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -670,7 +671,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -687,7 +688,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -708,7 +709,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS })); - testWidgets('at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -737,7 +738,7 @@ void main() { return SingleActivator(trigger, meta: isApple, alt: !isApple); } - testWidgets('alt-backspace', (WidgetTester tester) async { + testWidgetsWithLeakTracking('alt-backspace', (WidgetTester tester) async { controller.text = testText; // Place the caret before "people". controller.selection = const TextSelection.collapsed( @@ -762,7 +763,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('softwrap line boundary, upstream', (WidgetTester tester) async { + testWidgetsWithLeakTracking('softwrap line boundary, upstream', (WidgetTester tester) async { controller.text = testSoftwrapText; // Place the caret at the end of the 2nd line. controller.selection = const TextSelection.collapsed( @@ -786,7 +787,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('softwrap line boundary, downstream', (WidgetTester tester) async { + testWidgetsWithLeakTracking('softwrap line boundary, downstream', (WidgetTester tester) async { controller.text = testSoftwrapText; // Place the caret at the beginning of the 3rd line. controller.selection = const TextSelection.collapsed( @@ -803,7 +804,7 @@ void main() { expect(controller.text, testSoftwrapText); }, variant: TargetPlatformVariant.all()); - testWidgets('readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 29, @@ -821,7 +822,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -844,7 +845,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -867,7 +868,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -888,7 +889,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -917,7 +918,7 @@ void main() { return SingleActivator(trigger, meta: isApple, alt: !isApple); } - testWidgets('alt-delete', (WidgetTester tester) async { + testWidgetsWithLeakTracking('alt-delete', (WidgetTester tester) async { controller.text = testText; // Place the caret after "all". controller.selection = const TextSelection.collapsed( @@ -942,7 +943,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('softwrap line boundary, upstream', (WidgetTester tester) async { + testWidgetsWithLeakTracking('softwrap line boundary, upstream', (WidgetTester tester) async { controller.text = testSoftwrapText; // Place the caret at the end of the 2nd line. controller.selection = const TextSelection.collapsed( @@ -961,7 +962,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('softwrap line boundary, downstream', (WidgetTester tester) async { + testWidgetsWithLeakTracking('softwrap line boundary, downstream', (WidgetTester tester) async { controller.text = testSoftwrapText; // Place the caret at the beginning of the 3rd line. controller.selection = const TextSelection.collapsed( @@ -984,7 +985,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('readonly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('readonly', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 23, @@ -1002,7 +1003,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -1025,7 +1026,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -1042,7 +1043,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('inside of a cluster', (WidgetTester tester) async { + testWidgetsWithLeakTracking('inside of a cluster', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 1, @@ -1063,7 +1064,7 @@ void main() { ); }, variant: TargetPlatformVariant.all()); - testWidgets('at cluster boundary', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at cluster boundary', (WidgetTester tester) async { controller.text = testCluster; controller.selection = const TextSelection.collapsed( offset: 8, @@ -1089,7 +1090,7 @@ void main() { group('left', () { const LogicalKeyboardKey trigger = LogicalKeyboardKey.arrowLeft; - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -1109,7 +1110,7 @@ void main() { } }, variant: TargetPlatformVariant.all()); - testWidgets('base arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('base arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 20, @@ -1123,7 +1124,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('word modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1137,7 +1138,7 @@ void main() { )); }, variant: allExceptApple); - testWidgets('line modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1155,7 +1156,7 @@ void main() { group('right', () { const LogicalKeyboardKey trigger = LogicalKeyboardKey.arrowRight; - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -1172,7 +1173,7 @@ void main() { } }, variant: TargetPlatformVariant.all()); - testWidgets('base arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('base arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 20, @@ -1186,7 +1187,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('word modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1200,7 +1201,7 @@ void main() { )); }, variant: allExceptApple); - testWidgets('line modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1217,7 +1218,7 @@ void main() { }); group('With initial non-collapsed selection', () { - testWidgets('base arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('base arrow key movement', (WidgetTester tester) async { controller.text = testText; // The word "all" is selected. controller.selection = const TextSelection( @@ -1269,7 +1270,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('word modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; // "good" to "come" is selected. controller.selection = const TextSelection( @@ -1322,7 +1323,7 @@ void main() { )); }, variant: allExceptApple); - testWidgets('line modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; // "good" to "come" is selected. controller.selection = const TextSelection( @@ -1378,7 +1379,7 @@ void main() { }); group('vertical movement', () { - testWidgets('at start', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at start', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -1411,7 +1412,7 @@ void main() { } }, variant: TargetPlatformVariant.all()); - testWidgets('at end', (WidgetTester tester) async { + testWidgetsWithLeakTracking('at end', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 72, @@ -1438,7 +1439,7 @@ void main() { } }, variant: TargetPlatformVariant.all()); - testWidgets('run', (WidgetTester tester) async { + testWidgetsWithLeakTracking('run', (WidgetTester tester) async { controller.text = 'aa\n' // 3 'a\n' // 3 + 2 = 5 @@ -1525,7 +1526,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('run with page down/up', (WidgetTester tester) async { + testWidgetsWithLeakTracking('run with page down/up', (WidgetTester tester) async { controller.text = 'aa\n' // 3 'a\n' // 3 + 2 = 5 @@ -1560,7 +1561,7 @@ void main() { )); }, variant: TargetPlatformVariant.all(excluding: {TargetPlatform.iOS, TargetPlatform.macOS})); // intended: on macOS Page Up/Down only scrolls - testWidgets('run can be interrupted by layout changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('run can be interrupted by layout changes', (WidgetTester tester) async { controller.text = 'aa\n' // 3 'a\n' // 3 + 2 = 5 @@ -1589,7 +1590,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('run can be interrupted by selection changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('run can be interrupted by selection changes', (WidgetTester tester) async { controller.text = 'aa\n' // 3 'a\n' // 3 + 2 = 5 @@ -1624,7 +1625,7 @@ void main() { )); }, variant: TargetPlatformVariant.all()); - testWidgets('long run with fractional text height', (WidgetTester tester) async { + testWidgetsWithLeakTracking('long run with fractional text height', (WidgetTester tester) async { controller.text = "${'źdźbło\n' * 49}źdźbło"; controller.selection = const TextSelection.collapsed(offset: 2); await tester.pumpWidget(buildEditableText(style: const TextStyle(fontSize: 13.0, height: 1.17))); @@ -1658,7 +1659,7 @@ void main() { group('macOS shortcuts', () { final TargetPlatformVariant macOSOnly = TargetPlatformVariant.only(TargetPlatform.macOS); - testWidgets('word modifier + arrowLeft', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrowLeft', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1672,7 +1673,7 @@ void main() { )); }, variant: macOSOnly); - testWidgets('word modifier + arrowRight', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrowRight', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1686,7 +1687,7 @@ void main() { )); }, variant: macOSOnly); - testWidgets('line modifier + arrowLeft', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrowLeft', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1700,7 +1701,7 @@ void main() { )); }, variant: macOSOnly); - testWidgets('line modifier + arrowRight', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrowRight', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1715,7 +1716,7 @@ void main() { )); }, variant: macOSOnly); - testWidgets('word modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; // "good" to "come" is selected. controller.selection = const TextSelection( @@ -1768,7 +1769,7 @@ void main() { )); }, variant: macOSOnly); - testWidgets('line modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; // "good" to "come" is selected. controller.selection = const TextSelection( @@ -1828,7 +1829,7 @@ void main() { const TargetPlatformVariant appleOnly = TargetPlatformVariant({ TargetPlatform.macOS, TargetPlatform.iOS }); group('macOS shortcuts', () { - testWidgets('word modifier + arrowLeft', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrowLeft', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1840,7 +1841,7 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 7)); }, variant: appleOnly); - testWidgets('word modifier + arrowRight', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrowRight', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 7, // Before the first "the" @@ -1852,7 +1853,7 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 7)); }, variant: appleOnly); - testWidgets('line modifier + arrowLeft', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrowLeft', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1864,7 +1865,7 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 24,)); }, variant: appleOnly); - testWidgets('line modifier + arrowRight', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrowRight', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 24, // Before the "good". @@ -1878,7 +1879,7 @@ void main() { )); }, variant: appleOnly); - testWidgets('word modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('word modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection( baseOffset: 24, @@ -1931,7 +1932,7 @@ void main() { )); }, variant: appleOnly); - testWidgets('line modifier + arrow key movement', (WidgetTester tester) async { + testWidgetsWithLeakTracking('line modifier + arrow key movement', (WidgetTester tester) async { controller.text = testText; // "good" to "come" is selected. controller.selection = const TextSelection( @@ -1988,7 +1989,7 @@ void main() { }, variant: appleOnly); }); - testWidgets('vertical movement outside of selection', + testWidgetsWithLeakTracking('vertical movement outside of selection', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( @@ -2014,7 +2015,7 @@ void main() { } }, variant: TargetPlatformVariant.all()); - testWidgets('select all non apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all non apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -2027,7 +2028,7 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 0)); }, variant: allExceptApple); - testWidgets('select all apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed( offset: 0, @@ -2040,7 +2041,7 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 0)); }, variant: appleOnly); - testWidgets('copy non apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('copy non apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, @@ -2055,7 +2056,7 @@ void main() { expect(clipboardData['text'], 'empty'); }, variant: allExceptApple); - testWidgets('copy apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('copy apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, @@ -2070,7 +2071,7 @@ void main() { expect(clipboardData['text'], 'empty'); }, variant: appleOnly); - testWidgets('cut non apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cut non apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, @@ -2089,7 +2090,7 @@ void main() { )); }, variant: allExceptApple); - testWidgets('cut apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cut apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, @@ -2108,7 +2109,7 @@ void main() { )); }, variant: appleOnly); - testWidgets('paste non apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('paste non apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed(offset: 0); mockClipboard.clipboardData = { @@ -2121,7 +2122,7 @@ void main() { expect(controller.text, testText); }, variant: allExceptApple); - testWidgets('paste apple', (WidgetTester tester) async { + testWidgetsWithLeakTracking('paste apple', (WidgetTester tester) async { controller.text = testText; controller.selection = const TextSelection.collapsed(offset: 0); mockClipboard.clipboardData = { @@ -2137,7 +2138,7 @@ void main() { }, skip: !kIsWeb);// [intended] specific tests target web. group('Web does accept', () { - testWidgets('select up', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select up', (WidgetTester tester) async { const SingleActivator selectUp = SingleActivator(LogicalKeyboardKey.arrowUp, shift: true); controller.text = testVerticalText; @@ -2159,7 +2160,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select down', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select down', (WidgetTester tester) async { const SingleActivator selectDown = SingleActivator(LogicalKeyboardKey.arrowDown, shift: true); controller.text = testVerticalText; @@ -2181,7 +2182,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select all up', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all up', (WidgetTester tester) async { final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS; final SingleActivator selectAllUp = isMacOS ? const SingleActivator(LogicalKeyboardKey.arrowUp, @@ -2207,7 +2208,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select all down', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all down', (WidgetTester tester) async { final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS; final SingleActivator selectAllDown = isMacOS ? const SingleActivator(LogicalKeyboardKey.arrowDown, @@ -2233,7 +2234,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select left', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select left', (WidgetTester tester) async { const SingleActivator selectLeft = SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true); controller.text = 'testing'; @@ -2253,7 +2254,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select right', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select right', (WidgetTester tester) async { const SingleActivator selectRight = SingleActivator(LogicalKeyboardKey.arrowRight, shift: true); controller.text = 'testing'; @@ -2273,7 +2274,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets( + testWidgetsWithLeakTracking( 'select left should not expand selection if selection is disabled', (WidgetTester tester) async { const SingleActivator selectLeft = @@ -2296,7 +2297,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets( + testWidgetsWithLeakTracking( 'select right should not expand selection if selection is disabled', (WidgetTester tester) async { const SingleActivator selectRight = @@ -2317,7 +2318,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select all left', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all left', (WidgetTester tester) async { final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS; final SingleActivator selectAllLeft = isMacOS ? const SingleActivator(LogicalKeyboardKey.arrowLeft, @@ -2341,7 +2342,7 @@ void main() { ); }, variant: TargetPlatformVariant.desktop()); - testWidgets('select all right', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all right', (WidgetTester tester) async { final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS; final SingleActivator selectAllRight = isMacOS ? const SingleActivator(LogicalKeyboardKey.arrowRight, diff --git a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart index 2c4a9edbc557..33b51dc4ccd4 100644 --- a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart +++ b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/src/foundation/constants.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; class _TestSliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { _TestSliverPersistentHeaderDelegate({ @@ -40,11 +41,23 @@ class _TestSliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate void main() { const TextStyle textStyle = TextStyle(); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); - final FocusNode focusNode = FocusNode(); - testWidgets('tapping on a partly visible editable brings it fully on screen', (WidgetTester tester) async { + late TextEditingController controller; + late FocusNode focusNode; + + setUp(() { + controller = TextEditingController(); + focusNode = FocusNode(); + }); + + tearDown(() { + controller.dispose(); + focusNode.dispose(); + }); + + testWidgetsWithLeakTracking('tapping on a partly visible editable brings it fully on screen', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); + addTearDown(scrollController.dispose); await tester.pumpWidget(MaterialApp( home: Center( @@ -80,10 +93,9 @@ void main() { expect(scrollController.offset, 0.0); }); - testWidgets('tapping on a partly visible editable brings it fully on screen with scrollInsets', (WidgetTester tester) async { + testWidgetsWithLeakTracking('tapping on a partly visible editable brings it fully on screen with scrollInsets', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); await tester.pumpWidget(MaterialApp( home: Center( @@ -126,10 +138,9 @@ void main() { expect(scrollController.offset, greaterThan(200.0 - 50.0 - 5.0)); }); - testWidgets('editable comes back on screen when entering text while it is off-screen', (WidgetTester tester) async { + testWidgetsWithLeakTracking('editable comes back on screen when entering text while it is off-screen', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(initialScrollOffset: 100.0); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); await tester.pumpWidget(MaterialApp( home: Center( @@ -173,12 +184,10 @@ void main() { expect(find.byType(EditableText), findsOneWidget); }); - testWidgets('entering text does not scroll when scrollPhysics.allowImplicitScrolling = false', (WidgetTester tester) async { + testWidgetsWithLeakTracking('entering text does not scroll when scrollPhysics.allowImplicitScrolling = false', (WidgetTester tester) async { // regression test for https://github.com/flutter/flutter/issues/19523 - final ScrollController scrollController = ScrollController(initialScrollOffset: 100.0); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); await tester.pumpWidget(MaterialApp( home: Center( @@ -223,11 +232,10 @@ void main() { expect(find.byType(EditableText), findsNothing); }); - testWidgets('entering text does not scroll a surrounding PageView', (WidgetTester tester) async { + testWidgetsWithLeakTracking('entering text does not scroll a surrounding PageView', (WidgetTester tester) async { // regression test for https://github.com/flutter/flutter/issues/19523 - - final TextEditingController textController = TextEditingController(); final PageController pageController = PageController(initialPage: 1); + addTearDown(pageController.dispose); await tester.pumpWidget( MaterialApp( @@ -245,7 +253,7 @@ void main() { ColoredBox( color: Colors.green, child: TextField( - controller: textController, + controller: controller, ), ), Container( @@ -261,7 +269,7 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); await tester.pumpAndSettle(); - expect(textController.text, ''); + expect(controller.text, ''); tester.testTextInput.enterText('H'); final int frames = await tester.pumpAndSettle(); @@ -269,13 +277,12 @@ void main() { // that the surrounding PageView is incorrectly scrolling back-and-forth. expect(frames, 1); - expect(textController.text, 'H'); + expect(controller.text, 'H'); }); - testWidgets('focused multi-line editable scrolls caret back into view when typing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('focused multi-line editable scrolls caret back into view when typing', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); controller.text = "Start${'\n' * 39}End"; await tester.pumpWidget(MaterialApp( @@ -322,10 +329,9 @@ void main() { expect(scrollController.offset, greaterThan(0.0)); }); - testWidgets('focused multi-line editable does not scroll to old position when non-collapsed selection set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('focused multi-line editable does not scroll to old position when non-collapsed selection set', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); final String text = "Start${'\n' * 39}End"; controller.value = TextEditingValue(text: text, selection: TextSelection.collapsed(offset: text.length - 3)); @@ -374,11 +380,9 @@ void main() { expect(scrollController.offset, 28.0); }); - testWidgets('scrolls into view with scrollInserts after the keyboard pops up', (WidgetTester tester) async { + testWidgetsWithLeakTracking('scrolls into view with scrollInserts after the keyboard pops up', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - + addTearDown(scrollController.dispose); const Key container = Key('container'); await tester.pumpWidget(MaterialApp( @@ -418,15 +422,14 @@ void main() { expect(find.byKey(container), findsNothing); }); - testWidgets( + testWidgetsWithLeakTracking( 'A pinned persistent header should not scroll when its descendant EditableText gains focus', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/25507. - ScrollController controller; - final TextEditingController textEditingController = TextEditingController(); - final FocusNode focusNode = FocusNode(); - + final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); const Key headerKey = Key('header'); + await tester.pumpWidget( MaterialApp( home: Center( @@ -434,7 +437,7 @@ void main() { height: 600.0, width: 600.0, child: CustomScrollView( - controller: controller = ScrollController(), + controller: scrollController, slivers: List.generate(50, (int i) { return i == 10 ? SliverPersistentHeader( @@ -447,7 +450,7 @@ void main() { child: EditableText( key: headerKey, backgroundCursorColor: Colors.grey, - controller: textEditingController, + controller: controller, focusNode: focusNode, style: textStyle, cursorColor: cursorColor, @@ -469,24 +472,23 @@ void main() { ); // The persistent header should now be pinned at the top. - controller.jumpTo(100.0 * 15); + scrollController.jumpTo(100.0 * 15); await tester.pumpAndSettle(); - expect(controller.offset, 100.0 * 15); + expect(scrollController.offset, 100.0 * 15); focusNode.requestFocus(); await tester.pumpAndSettle(); // The scroll offset should remain the same. - expect(controller.offset, 100.0 * 15); + expect(scrollController.offset, 100.0 * 15); }, ); - testWidgets( + testWidgetsWithLeakTracking( 'A pinned persistent header should not scroll when its descendant EditableText gains focus (no animation)', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/25507. - ScrollController controller; - final TextEditingController textEditingController = TextEditingController(); - final FocusNode focusNode = FocusNode(); + final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); const Key headerKey = Key('header'); await tester.pumpWidget( @@ -496,7 +498,7 @@ void main() { height: 600.0, width: 600.0, child: CustomScrollView( - controller: controller = ScrollController(), + controller: scrollController, slivers: List.generate(50, (int i) { return i == 10 ? SliverPersistentHeader( @@ -510,7 +512,7 @@ void main() { child: EditableText( key: headerKey, backgroundCursorColor: Colors.grey, - controller: textEditingController, + controller: controller, focusNode: focusNode, style: textStyle, cursorColor: cursorColor, @@ -532,20 +534,19 @@ void main() { ); // The persistent header should now be pinned at the top. - controller.jumpTo(100.0 * 15); + scrollController.jumpTo(100.0 * 15); await tester.pumpAndSettle(); - expect(controller.offset, 100.0 * 15); + expect(scrollController.offset, 100.0 * 15); focusNode.requestFocus(); await tester.pumpAndSettle(); // The scroll offset should remain the same. - expect(controller.offset, 100.0 * 15); + expect(scrollController.offset, 100.0 * 15); }, ); void testShowCaretOnScreen({ required bool readOnly }) { group('EditableText._showCaretOnScreen, readOnly=$readOnly', () { - final TextEditingController textEditingController = TextEditingController(); final TextInputFormatter rejectEverythingFormatter = TextInputFormatter.withFunction((TextEditingValue old, TextEditingValue value) => old); bool isCaretOnScreen(WidgetTester tester) { @@ -574,7 +575,7 @@ void main() { const SizedBox(height: 599), EditableText( backgroundCursorColor: Colors.grey, - controller: textEditingController, + controller: controller, scrollController: editableScrollController, inputFormatters: [if (rejectUserInputs) rejectEverythingFormatter], focusNode: focusNode, @@ -588,11 +589,13 @@ void main() { ); } - testWidgets('focus-triggered showCaretOnScreen', (WidgetTester tester) async { - textEditingController.text = 'a' * 100; - textEditingController.selection = const TextSelection.collapsed(offset: 100); + testWidgetsWithLeakTracking('focus-triggered showCaretOnScreen', (WidgetTester tester) async { + controller.text = 'a' * 100; + controller.selection = const TextSelection.collapsed(offset: 100); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); final ScrollController editableScrollController = ScrollController(); + addTearDown(editableScrollController.dispose); await tester.pumpWidget( buildEditableText( @@ -617,11 +620,13 @@ void main() { expect(editableScrollController.offset, readOnly ? 0.0 : greaterThan(0.0)); }); - testWidgets('selection-triggered showCaretOnScreen: virtual keyboard', (WidgetTester tester) async { - textEditingController.text = 'a' * 100; - textEditingController.selection = const TextSelection.collapsed(offset: 80); + testWidgetsWithLeakTracking('selection-triggered showCaretOnScreen: virtual keyboard', (WidgetTester tester) async { + controller.text = 'a' * 100; + controller.selection = const TextSelection.collapsed(offset: 80); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); final ScrollController editableScrollController = ScrollController(); + addTearDown(editableScrollController.dispose); await tester.pumpWidget( buildEditableText( @@ -675,11 +680,13 @@ void main() { expect(editableScrollController.offset, readOnly && !kIsWeb ? 0.0 : greaterThan(0.0)); }); - testWidgets('selection-triggered showCaretOnScreen: text selection delegate', (WidgetTester tester) async { - textEditingController.text = 'a' * 100; - textEditingController.selection = const TextSelection.collapsed(offset: 80); + testWidgetsWithLeakTracking('selection-triggered showCaretOnScreen: text selection delegate', (WidgetTester tester) async { + controller.text = 'a' * 100; + controller.selection = const TextSelection.collapsed(offset: 80); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); final ScrollController editableScrollController = ScrollController(); + addTearDown(editableScrollController.dispose); await tester.pumpWidget( buildEditableText( @@ -739,10 +746,11 @@ void main() { }); // Regression text for https://github.com/flutter/flutter/pull/74722. - testWidgets('does NOT randomly trigger when cursor blinks', (WidgetTester tester) async { - textEditingController.text = 'a' * 100; - textEditingController.selection = const TextSelection.collapsed(offset: 0); + testWidgetsWithLeakTracking('does NOT randomly trigger when cursor blinks', (WidgetTester tester) async { + controller.text = 'a' * 100; + controller.selection = const TextSelection.collapsed(offset: 0); final ScrollController editableScrollController = ScrollController(); + addTearDown(editableScrollController.dispose); final bool deterministicCursor = EditableText.debugDeterministicCursor; EditableText.debugDeterministicCursor = false; @@ -751,7 +759,7 @@ void main() { home: Scaffold( body: EditableText( backgroundCursorColor: Colors.grey, - controller: textEditingController, + controller: controller, scrollController: editableScrollController, focusNode: focusNode, style: textStyle, @@ -770,7 +778,7 @@ void main() { expect(editableScrollController.offset, 0.0); // Change the text but keep the cursor location. - state.updateEditingValue(textEditingController.value.copyWith( + state.updateEditingValue(controller.value.copyWith( text: 'a' * 101, )); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 4efaf4992cbd..dc5b7fb797ad 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -11,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/clipboard_utils.dart'; import 'editable_text_utils.dart'; @@ -43,9 +44,6 @@ class _MatchesMethodCall extends Matcher { } } -late TextEditingController controller; -final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node'); -final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node'); const TextStyle textStyle = TextStyle(); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); @@ -63,21 +61,29 @@ TextEditingValue collapsedAtEnd(String text) { } void main() { + late TextEditingController controller; + late FocusNode focusNode; + late FocusScopeNode focusScopeNode; + setUp(() async { final MockClipboard mockClipboard = MockClipboard(); TestWidgetsFlutterBinding.ensureInitialized() .defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); debugResetSemanticsIdCounter(); - controller = TextEditingController(); // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); + controller = TextEditingController(); + focusNode = FocusNode(debugLabel: 'EditableText Node'); + focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node'); }); tearDown(() { TestWidgetsFlutterBinding.ensureInitialized() .defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); controller.dispose(); + focusNode.dispose(); + focusScopeNode.dispose(); }); // Tests that the desired keyboard action button is requested. @@ -119,12 +125,11 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName)); } - testWidgets( + testWidgetsWithLeakTracking( 'Tapping the Live Text button calls onLiveTextInput', (WidgetTester tester) async { bool invokedLiveTextInputSuccessfully = false; final GlobalKey key = GlobalKey(); - final TextEditingController controller = TextEditingController(text: ''); await tester.pumpWidget( MaterialApp( home: Align( @@ -136,7 +141,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.subtitle1!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -189,15 +194,20 @@ void main() { ); // Regression test for https://github.com/flutter/flutter/issues/126312. - testWidgets('when open input connection in didUpdateWidget, should not throw', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when open input connection in didUpdateWidget, should not throw', (WidgetTester tester) async { final Key key = GlobalKey(); + final TextEditingController controller1 = TextEditingController(text: 'blah blah'); + addTearDown(controller1.dispose); + final TextEditingController controller2 = TextEditingController(text: 'blah blah'); + addTearDown(controller2.dispose); + await tester.pumpWidget( MaterialApp( home: EditableText( key: key, backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller1, focusNode: focusNode, readOnly: true, style: textStyle, @@ -219,7 +229,7 @@ void main() { child: EditableText( key: key, backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller2, focusNode: focusNode, style: textStyle, cursorColor: cursorColor, @@ -230,14 +240,13 @@ void main() { ); }); - testWidgets('Text with selection can be shown on the screen when the keyboard shown', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Text with selection can be shown on the screen when the keyboard shown', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/119628 addTearDown(tester.view.reset); final ScrollController scrollController = ScrollController(); - final TextEditingController textController = TextEditingController.fromValue( - const TextEditingValue(text: 'I love flutter'), - ); + addTearDown(scrollController.dispose); + controller.value = const TextEditingValue(text: 'I love flutter'); final Widget widget = MaterialApp( home: Scaffold( @@ -249,7 +258,7 @@ void main() { SizedBox( height: 20.0, child: EditableText( - controller: textController, + controller: controller, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(), @@ -265,9 +274,9 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); tester.view.viewInsets = const FakeViewPadding(bottom: 500); - textController.selection = TextSelection( + controller.selection = TextSelection( baseOffset: 0, - extentOffset: textController.text.length, + extentOffset: controller.text.length, ); await tester.pump(); @@ -278,10 +287,11 @@ void main() { }); // Related issue: https://github.com/flutter/flutter/issues/98115 - testWidgets('ScheduleShowCaretOnScreen with no animation when the view changes metrics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ScheduleShowCaretOnScreen with no animation when the view changes metrics', (WidgetTester tester) async { addTearDown(tester.view.reset); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); final Widget widget = MaterialApp( home: Scaffold( body: SingleChildScrollView( @@ -302,7 +312,7 @@ void main() { SizedBox( height: 20, child: EditableText( - controller: TextEditingController(), + controller: controller, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(), @@ -325,8 +335,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/34538. - testWidgets('RTL arabic correct caret placement after trailing whitespace', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('RTL arabic correct caret placement after trailing whitespace', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -379,7 +388,7 @@ void main() { expect(state.currentTextEditingValue.text, equals('گیگ ')); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/78550. - testWidgets('has expected defaults', (WidgetTester tester) async { + testWidgetsWithLeakTracking('has expected defaults', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -409,7 +418,7 @@ void main() { expect(editableText.textHeightBehavior, isNull); }); - testWidgets('when backgroundCursorColor is updated, RenderEditable should be updated', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when backgroundCursorColor is updated, RenderEditable should be updated', (WidgetTester tester) async { Widget buildWidget(Color backgroundCursorColor) { return MediaQuery( data: const MediaQueryData(), @@ -433,7 +442,7 @@ void main() { expect(render.backgroundCursorColor, Colors.green); }); - testWidgets('text keyboard is requested when maxLines is default', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text keyboard is requested when maxLines is default', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -465,7 +474,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.done')); }); - testWidgets('Keyboard is configured for "unspecified" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "unspecified" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.unspecified, @@ -473,7 +482,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "none" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "none" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.none, @@ -481,7 +490,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "done" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "done" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.done, @@ -489,7 +498,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "send" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "send" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.send, @@ -497,7 +506,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "go" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "go" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.go, @@ -505,7 +514,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "search" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "search" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.search, @@ -513,7 +522,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "send" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "send" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.send, @@ -521,7 +530,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "next" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "next" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.next, @@ -529,7 +538,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "previous" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "previous" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.previous, @@ -537,7 +546,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "continue" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "continue" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.continueAction, @@ -545,7 +554,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "join" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "join" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.join, @@ -553,7 +562,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "route" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "route" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.route, @@ -561,7 +570,7 @@ void main() { ); }); - testWidgets('Keyboard is configured for "emergencyCall" action when explicitly requested', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Keyboard is configured for "emergencyCall" action when explicitly requested', (WidgetTester tester) async { await desiredKeyboardActionIsRequested( tester: tester, action: TextInputAction.emergencyCall, @@ -569,7 +578,7 @@ void main() { ); }); - testWidgets('insertContent does not throw and parses data correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insertContent does not throw and parses data correctly', (WidgetTester tester) async { String? latestUri; await tester.pumpWidget( MediaQuery( @@ -625,7 +634,7 @@ void main() { expect(latestUri, equals(uri)); }); - testWidgets('onAppPrivateCommand does not throw', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onAppPrivateCommand does not throw', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -673,7 +682,7 @@ void main() { }); group('Infer keyboardType from autofillHints', () { - testWidgets( + testWidgetsWithLeakTracking( 'infer keyboard types from autofillHints: ios', (WidgetTester tester) async { await tester.pumpWidget( @@ -712,7 +721,7 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets( + testWidgetsWithLeakTracking( 'infer keyboard types from autofillHints: non-ios', (WidgetTester tester) async { await tester.pumpWidget( @@ -745,7 +754,7 @@ void main() { }, ); - testWidgets( + testWidgetsWithLeakTracking( 'inferred keyboard types can be overridden: ios', (WidgetTester tester) async { await tester.pumpWidget( @@ -780,7 +789,7 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets( + testWidgetsWithLeakTracking( 'inferred keyboard types can be overridden: non-ios', (WidgetTester tester) async { await tester.pumpWidget( @@ -815,7 +824,7 @@ void main() { ); }); - testWidgets('multiline keyboard is requested when set explicitly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('multiline keyboard is requested when set explicitly', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -846,7 +855,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.newline')); }); - testWidgets('EditableText sends enableInteractiveSelection to config', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText sends enableInteractiveSelection to config', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -898,7 +907,7 @@ void main() { expect(state.textInputConfiguration.enableInteractiveSelection, isFalse); }); - testWidgets('selection persists when unfocused', (WidgetTester tester) async { + testWidgetsWithLeakTracking('selection persists when unfocused', (WidgetTester tester) async { const TextEditingValue value = TextEditingValue( text: 'test test', selection: TextSelection(affinity: TextAffinity.upstream, baseOffset: 5, extentOffset: 7), @@ -952,7 +961,7 @@ void main() { expect(focusNode.hasFocus, isFalse); }); - testWidgets('selection rects re-sent when refocused', (WidgetTester tester) async { + testWidgetsWithLeakTracking('selection rects re-sent when refocused', (WidgetTester tester) async { final List> log = >[]; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { if (methodCall.method == 'TextInput.setSelectionRects') { @@ -969,8 +978,8 @@ void main() { return null; }); - final TextEditingController controller = TextEditingController(); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); controller.text = 'Text1'; Future pumpEditableText({ double? width, double? height, TextAlign textAlign = TextAlign.start }) async { @@ -1034,7 +1043,7 @@ void main() { // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('EditableText does not derive selection color from DefaultSelectionStyle', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText does not derive selection color from DefaultSelectionStyle', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/103341. const TextEditingValue value = TextEditingValue( text: 'test test', @@ -1065,7 +1074,7 @@ void main() { expect(state.renderEditable.selectionColor, null); }); - testWidgets('visiblePassword keyboard is requested when set explicitly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('visiblePassword keyboard is requested when set explicitly', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1096,8 +1105,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.done')); }); - testWidgets('enableSuggestions flag is sent to the engine properly', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('enableSuggestions flag is sent to the engine properly', (WidgetTester tester) async { const bool enableSuggestions = false; await tester.pumpWidget( MediaQuery( @@ -1126,8 +1134,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['enableSuggestions'], enableSuggestions); }); - testWidgets('enableIMEPersonalizedLearning flag is sent to the engine properly', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('enableIMEPersonalizedLearning flag is sent to the engine properly', (WidgetTester tester) async { const bool enableIMEPersonalizedLearning = false; await tester.pumpWidget( MediaQuery( @@ -1157,8 +1164,7 @@ void main() { }); group('smartDashesType and smartQuotesType', () { - testWidgets('sent to the engine properly', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('sent to the engine properly', (WidgetTester tester) async { const SmartDashesType smartDashesType = SmartDashesType.disabled; const SmartQuotesType smartQuotesType = SmartQuotesType.disabled; await tester.pumpWidget( @@ -1190,8 +1196,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['smartQuotesType'], smartQuotesType.index.toString()); }); - testWidgets('default to true when obscureText is false', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('default to true when obscureText is false', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1219,8 +1224,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['smartQuotesType'], '1'); }); - testWidgets('default to false when obscureText is true', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('default to false when obscureText is true', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1250,12 +1254,9 @@ void main() { }); }); - testWidgets('selection overlay will update when text grow bigger', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController.fromValue( - const TextEditingValue( - text: 'initial value', - ), - ); + testWidgetsWithLeakTracking('selection overlay will update when text grow bigger', (WidgetTester tester) async { + controller.value = const TextEditingValue(text: 'initial value'); + Future pumpEditableTextWithTextStyle(TextStyle style) async { await tester.pumpWidget( MaterialApp( @@ -1310,9 +1311,18 @@ void main() { expect(handles[1].localToGlobal(Offset.zero), const Offset(197.0, 17.0)); }); - testWidgets('can update style of previous activated EditableText', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can update style of previous activated EditableText', (WidgetTester tester) async { + final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); + final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); + final TextEditingController controller3 = TextEditingController(); + addTearDown(controller3.dispose); + final TextEditingController controller4 = TextEditingController(); + addTearDown(controller4.dispose); final Key key1 = UniqueKey(); final Key key2 = UniqueKey(); + await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1325,7 +1335,7 @@ void main() { children: [ EditableText( key: key1, - controller: TextEditingController(), + controller: controller1, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(fontSize: 9), @@ -1333,7 +1343,7 @@ void main() { ), EditableText( key: key2, - controller: TextEditingController(), + controller: controller2, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(fontSize: 9), @@ -1368,7 +1378,7 @@ void main() { children: [ EditableText( key: key1, - controller: TextEditingController(), + controller: controller3, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(fontSize: 20), @@ -1376,7 +1386,7 @@ void main() { ), EditableText( key: key2, - controller: TextEditingController(), + controller: controller4, backgroundCursorColor: Colors.grey, focusNode: focusNode, style: const TextStyle(fontSize: 9), @@ -1393,7 +1403,7 @@ void main() { expect(tester.takeException(), null); }); - testWidgets('Multiline keyboard with newline action is requested when maxLines = null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Multiline keyboard with newline action is requested when maxLines = null', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1424,7 +1434,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.newline')); }); - testWidgets('Text keyboard is requested when explicitly set and maxLines = null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Text keyboard is requested when explicitly set and maxLines = null', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1456,7 +1466,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.done')); }); - testWidgets('Correct keyboard is requested when set explicitly and maxLines > 1', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Correct keyboard is requested when set explicitly and maxLines > 1', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1488,7 +1498,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.done')); }); - testWidgets('multiline keyboard is requested when set implicitly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('multiline keyboard is requested when set implicitly', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1519,7 +1529,7 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals('TextInputAction.newline')); }); - testWidgets('single line inputs have correct default keyboard', (WidgetTester tester) async { + testWidgetsWithLeakTracking('single line inputs have correct default keyboard', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1550,7 +1560,7 @@ void main() { }); // Test case for https://github.com/flutter/flutter/issues/123523. - testWidgets( + testWidgetsWithLeakTracking( 'The focus and callback behavior are correct when TextInputClient.onConnectionClosed message received', (WidgetTester tester) async { bool onSubmittedInvoked = false; @@ -1600,7 +1610,7 @@ void main() { } }); - testWidgets('connection is closed when TextInputClient.onConnectionClosed message received', (WidgetTester tester) async { + testWidgetsWithLeakTracking('connection is closed when TextInputClient.onConnectionClosed message received', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1643,7 +1653,7 @@ void main() { expect(tester.testTextInput.log, isEmpty); }); - testWidgets('closed connection reopened when user focused', (WidgetTester tester) async { + testWidgetsWithLeakTracking('closed connection reopened when user focused', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -1694,7 +1704,7 @@ void main() { expect(state.wantKeepAlive, true); }); - testWidgets('closed connection reopened when user focused on another field', (WidgetTester tester) async { + testWidgetsWithLeakTracking('closed connection reopened when user focused on another field', (WidgetTester tester) async { final EditableText testNameField = EditableText( backgroundCursorColor: Colors.grey, @@ -1769,7 +1779,7 @@ void main() { expect(state.wantKeepAlive, true); }); - testWidgets( + testWidgetsWithLeakTracking( 'kept-alive EditableText does not crash when layout is skipped', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84896. @@ -1847,7 +1857,7 @@ void main() { // cut. It might also provide additional functionality depending on the // browser (such as translation). Due to this, in browsers, we should not // show a Flutter toolbar for the editable text elements. - testWidgets('can show toolbar when there is text and a selection', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can show toolbar when there is text and a selection', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -1912,7 +1922,7 @@ void main() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.contextMenu, null); }); - testWidgets('web can show flutter context menu when the browser context menu is disabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('web can show flutter context menu when the browser context menu is disabled', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -1960,7 +1970,7 @@ void main() { ); }); - testWidgets('can hide toolbar with DismissIntent', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can hide toolbar with DismissIntent', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -1995,7 +2005,7 @@ void main() { expect(find.text('Paste'), findsNothing); }); - testWidgets('toolbar hidden on mobile when orientation changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('toolbar hidden on mobile when orientation changes', (WidgetTester tester) async { addTearDown(tester.view.reset); await tester.pumpWidget( @@ -2043,7 +2053,7 @@ void main() { // toolbar. Until we change that, this test should remain skipped. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.android })); // [intended] - testWidgets('Paste is shown only when there is something to paste', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Paste is shown only when there is something to paste', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2093,8 +2103,10 @@ void main() { expect(find.text('Paste'), findsNothing); }); - testWidgets('Copy selection does not collapse selection on desktop and iOS', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Copy selection does not collapse selection on desktop and iOS', (WidgetTester tester) async { final TextEditingController localController = TextEditingController(text: 'Hello world'); + addTearDown(localController.dispose); + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2131,8 +2143,10 @@ void main() { expect(find.text('Copy'), findsNothing); }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows })); // [intended] - testWidgets('Copy selection collapses selection and hides the toolbar on Android and Fuchsia', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Copy selection collapses selection and hides the toolbar on Android and Fuchsia', (WidgetTester tester) async { final TextEditingController localController = TextEditingController(text: 'Hello world'); + addTearDown(localController.dispose); + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2171,7 +2185,7 @@ void main() { expect(find.text('Copy'), findsNothing); }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia })); // [intended] - testWidgets('can show the toolbar after clearing all text', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can show the toolbar after clearing all text', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/35998. await tester.pumpWidget( MaterialApp( @@ -2210,12 +2224,14 @@ void main() { expect(find.text('Paste'), kIsWeb ? findsNothing : findsOneWidget); }); - testWidgets('can dynamically disable options in toolbar', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can dynamically disable options in toolbar', (WidgetTester tester) async { + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, toolbarOptions: const ToolbarOptions( copy: true, @@ -2246,13 +2262,15 @@ void main() { expect(find.text('Cut'), findsNothing); }); - testWidgets('can dynamically disable select all option in toolbar - cupertino', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can dynamically disable select all option in toolbar - cupertino', (WidgetTester tester) async { // Regression test: https://github.com/flutter/flutter/issues/40711 + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, toolbarOptions: ToolbarOptions.empty, style: textStyle, @@ -2275,13 +2293,15 @@ void main() { expect(find.text('Cut'), findsNothing); }); - testWidgets('can dynamically disable select all option in toolbar - material', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can dynamically disable select all option in toolbar - material', (WidgetTester tester) async { // Regression test: https://github.com/flutter/flutter/issues/40711 + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, toolbarOptions: const ToolbarOptions( copy: true, @@ -2311,12 +2331,14 @@ void main() { expect(find.text('Cut'), findsNothing); }); - testWidgets('cut and paste are disabled in read only mode even if explicitly set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cut and paste are disabled in read only mode even if explicitly set', (WidgetTester tester) async { + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, readOnly: true, toolbarOptions: const ToolbarOptions( @@ -2350,12 +2372,14 @@ void main() { expect(find.text('Cut'), findsNothing); }); - testWidgets('cut and copy are disabled in obscured mode even if explicitly set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cut and copy are disabled in obscured mode even if explicitly set', (WidgetTester tester) async { + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -2392,12 +2416,14 @@ void main() { expect(find.text('Cut'), findsNothing); }); - testWidgets('cut and copy do nothing in obscured mode even if explicitly called', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cut and copy do nothing in obscured mode even if explicitly called', (WidgetTester tester) async { + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, obscureText: true, style: textStyle, @@ -2432,12 +2458,14 @@ void main() { expect(data!.text, isEmpty); }); - testWidgets('select all does nothing if obscured and read-only, even if explicitly called', (WidgetTester tester) async { + testWidgetsWithLeakTracking('select all does nothing if obscured and read-only, even if explicitly called', (WidgetTester tester) async { + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, obscureText: true, readOnly: true, @@ -2458,11 +2486,13 @@ void main() { }); group('buttonItemsForToolbarOptions', () { - testWidgets('returns null when toolbarOptions are empty', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns null when toolbarOptions are empty', (WidgetTester tester) async { + controller.text = 'TEXT'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'TEXT'), + controller: controller, toolbarOptions: ToolbarOptions.empty, focusNode: focusNode, style: textStyle, @@ -2479,11 +2509,13 @@ void main() { expect(state.buttonItemsForToolbarOptions(), isNull); }); - testWidgets('returns empty array when only cut is selected in toolbarOptions but cut is not enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns empty array when only cut is selected in toolbarOptions but cut is not enabled', (WidgetTester tester) async { + controller.text = 'TEXT'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'TEXT'), + controller: controller, toolbarOptions: const ToolbarOptions(cut: true), readOnly: true, focusNode: focusNode, @@ -2502,9 +2534,9 @@ void main() { expect(state.buttonItemsForToolbarOptions(), isEmpty); }); - testWidgets('returns only cut button when only cut is selected in toolbarOptions and cut is enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns only cut button when only cut is selected in toolbarOptions and cut is enabled', (WidgetTester tester) async { const String text = 'TEXT'; - final TextEditingController controller = TextEditingController(text: text); + controller.text = text; await tester.pumpWidget( MaterialApp( @@ -2547,11 +2579,13 @@ void main() { expect(data!.text, equals(text)); }); - testWidgets('returns empty array when only copy is selected in toolbarOptions but copy is not enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns empty array when only copy is selected in toolbarOptions but copy is not enabled', (WidgetTester tester) async { + controller.text = 'TEXT'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'TEXT'), + controller: controller, toolbarOptions: const ToolbarOptions(copy: true), obscureText: true, focusNode: focusNode, @@ -2570,9 +2604,9 @@ void main() { expect(state.buttonItemsForToolbarOptions(), isEmpty); }); - testWidgets('returns only copy button when only copy is selected in toolbarOptions and copy is enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns only copy button when only copy is selected in toolbarOptions and copy is enabled', (WidgetTester tester) async { const String text = 'TEXT'; - final TextEditingController controller = TextEditingController(text: text); + controller.text = text; await tester.pumpWidget( MaterialApp( @@ -2615,11 +2649,13 @@ void main() { expect(data!.text, equals(text)); }); - testWidgets('returns empty array when only paste is selected in toolbarOptions but paste is not enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns empty array when only paste is selected in toolbarOptions but paste is not enabled', (WidgetTester tester) async { + controller.text = 'TEXT'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'TEXT'), + controller: controller, toolbarOptions: const ToolbarOptions(paste: true), readOnly: true, focusNode: focusNode, @@ -2638,9 +2674,9 @@ void main() { expect(state.buttonItemsForToolbarOptions(), isEmpty); }); - testWidgets('returns only paste button when only paste is selected in toolbarOptions and paste is enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns only paste button when only paste is selected in toolbarOptions and paste is enabled', (WidgetTester tester) async { const String text = 'TEXT'; - final TextEditingController controller = TextEditingController(text: text); + controller.text = text; await tester.pumpWidget( MaterialApp( @@ -2680,11 +2716,13 @@ void main() { expect(controller.text, equals(text + text)); }); - testWidgets('returns empty array when only selectAll is selected in toolbarOptions but selectAll is not enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns empty array when only selectAll is selected in toolbarOptions but selectAll is not enabled', (WidgetTester tester) async { + controller.text = 'TEXT'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'TEXT'), + controller: controller, toolbarOptions: const ToolbarOptions(selectAll: true), readOnly: true, obscureText: true, @@ -2704,9 +2742,9 @@ void main() { expect(state.buttonItemsForToolbarOptions(), isEmpty); }); - testWidgets('returns only selectAll button when only selectAll is selected in toolbarOptions and selectAll is enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('returns only selectAll button when only selectAll is selected in toolbarOptions and selectAll is enabled', (WidgetTester tester) async { const String text = 'TEXT'; - final TextEditingController controller = TextEditingController(text: text); + controller.text = text; await tester.pumpWidget( MaterialApp( @@ -2741,9 +2779,9 @@ void main() { }); }); - testWidgets('Handles the read-only flag correctly', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('Handles the read-only flag correctly', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2783,9 +2821,9 @@ void main() { } }); - testWidgets('Does not accept updates when read-only', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('Does not accept updates when read-only', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2824,12 +2862,10 @@ void main() { } }); - testWidgets('Read-only fields do not format text', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Read-only fields do not format text', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; late SelectionChangedCause selectionCause; - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2864,12 +2900,10 @@ void main() { } }); - testWidgets('Selection changes during Scribble interaction should have the scribble cause', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Selection changes during Scribble interaction should have the scribble cause', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; late SelectionChangedCause selectionCause; - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - await tester.pumpWidget( MaterialApp( home: EditableText( @@ -2912,9 +2946,8 @@ void main() { await tester.testTextInput.finishScribbleInteraction(); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); - testWidgets('Requests focus and changes the selection when onScribbleFocus is called', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('Requests focus and changes the selection when onScribbleFocus is called', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; late SelectionChangedCause selectionCause; await tester.pumpWidget( @@ -2944,9 +2977,8 @@ void main() { // will never be SelectionChangedCause.scribble. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('Declares itself for Scribble interaction if the bounds overlap the scribble rect and the widget is touchable', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('Declares itself for Scribble interaction if the bounds overlap the scribble rect and the widget is touchable', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; await tester.pumpWidget( MaterialApp( @@ -3038,9 +3070,8 @@ void main() { // never request the scribble elements. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('single line Scribble fields can show a horizontal placeholder', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('single line Scribble fields can show a horizontal placeholder', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; await tester.pumpWidget( MaterialApp( @@ -3113,9 +3144,8 @@ void main() { // will not handle placeholders. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('multiline Scribble fields can show a vertical placeholder', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); + testWidgetsWithLeakTracking('multiline Scribble fields can show a vertical placeholder', (WidgetTester tester) async { + controller.text = 'Lorem ipsum dolor sit amet'; await tester.pumpWidget( MaterialApp( @@ -3191,10 +3221,10 @@ void main() { // will not handle placeholders. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('Sends "updateConfig" when read-only flag is flipped', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Sends "updateConfig" when read-only flag is flipped', (WidgetTester tester) async { bool readOnly = true; late StateSetter setState; - final TextEditingController controller = TextEditingController(text: 'Lorem ipsum dolor sit amet'); + controller.text = 'Lorem ipsum dolor sit amet'; await tester.pumpWidget( MaterialApp( @@ -3229,10 +3259,10 @@ void main() { expect(tester.testTextInput.setClientArgs!['readOnly'], isFalse); }); - testWidgets('Sends "updateConfig" when obscureText is flipped', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Sends "updateConfig" when obscureText is flipped', (WidgetTester tester) async { bool obscureText = true; late StateSetter setState; - final TextEditingController controller = TextEditingController(text: 'Lorem'); + controller.text = 'Lorem'; await tester.pumpWidget( MaterialApp( @@ -3265,13 +3295,13 @@ void main() { expect(tester.testTextInput.setClientArgs!['obscureText'], isFalse); }); - testWidgets('Fires onChanged when text changes via TextSelectionOverlay', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Fires onChanged when text changes via TextSelectionOverlay', (WidgetTester tester) async { late String changedValue; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -3308,7 +3338,7 @@ void main() { TextInputAction.values.toSet(), ); - testWidgets('Handles focus correctly when action is invoked', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Handles focus correctly when action is invoked', (WidgetTester tester) async { // The expectations for each of the types of TextInputAction. const Map actionShouldLoseFocus = { TextInputAction.none: false, @@ -3335,7 +3365,6 @@ void main() { bool shouldFocusNext = false, bool shouldFocusPrevious = false, }) async { - final FocusNode focusNode = FocusNode(); final GlobalKey previousKey = GlobalKey(); final GlobalKey nextKey = GlobalKey(); @@ -3348,7 +3377,7 @@ void main() { ), EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3388,13 +3417,11 @@ void main() { } }, variant: focusVariants); - testWidgets('Does not lose focus by default when "done" action is pressed and onEditingComplete is provided', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - + testWidgetsWithLeakTracking('Does not lose focus by default when "done" action is pressed and onEditingComplete is provided', (WidgetTester tester) async { final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3422,16 +3449,14 @@ void main() { expect(focusNode.hasFocus, true); }); - testWidgets('When "done" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - + testWidgetsWithLeakTracking('When "done" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { bool onEditingCompleteCalled = false; bool onSubmittedCalled = false; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3462,16 +3487,14 @@ void main() { // and onSubmission callbacks. }); - testWidgets('When "next" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - + testWidgetsWithLeakTracking('When "next" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { bool onEditingCompleteCalled = false; bool onSubmittedCalled = false; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3502,16 +3525,14 @@ void main() { // and onSubmission callbacks. }); - testWidgets('When "newline" action is called on a Editable text with maxLines == 1 callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - + testWidgetsWithLeakTracking('When "newline" action is called on a Editable text with maxLines == 1 callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async { bool onEditingCompleteCalled = false; bool onSubmittedCalled = false; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3541,16 +3562,14 @@ void main() { // and onSubmission callbacks. }); - testWidgets('When "newline" action is called on a Editable text with maxLines != 1, onEditingComplete and onSubmitted callbacks are not invoked.', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - + testWidgetsWithLeakTracking('When "newline" action is called on a Editable text with maxLines != 1, onEditingComplete and onSubmitted callbacks are not invoked.', (WidgetTester tester) async { bool onEditingCompleteCalled = false; bool onSubmittedCalled = false; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -3581,7 +3600,7 @@ void main() { assert(!onEditingCompleteCalled); }); - testWidgets( + testWidgetsWithLeakTracking( 'finalizeEditing should reset the input connection when shouldUnfocus is true but the unfocus is cancelled', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84240 . @@ -3646,7 +3665,7 @@ void main() { ]))); }); - testWidgets( + testWidgetsWithLeakTracking( 'requesting focus in the onSubmitted callback should keep the onscreen keyboard visible', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/95154 . @@ -3689,10 +3708,11 @@ void main() { ]))); }); - testWidgets( + testWidgetsWithLeakTracking( 'iOS autocorrection rectangle should appear on demand and dismiss when the text changes or when focus is lost', (WidgetTester tester) async { const Color rectColor = Color(0xFFFF0000); + controller.text = 'ABCDEFG'; void verifyAutocorrectionRectVisibility({ required bool expectVisible }) { PaintPattern evaluate() { @@ -3721,9 +3741,6 @@ void main() { expect(findRenderEditable(tester), evaluate()); } - final FocusNode focusNode = FocusNode(); - final TextEditingController controller = TextEditingController(text: 'ABCDEFG'); - final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, @@ -3769,17 +3786,23 @@ void main() { verifyAutocorrectionRectVisibility(expectVisible: false); }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 5}, + ), ); - testWidgets('Changing controller updates EditableText', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Changing controller updates EditableText', (WidgetTester tester) async { final TextEditingController controller1 = TextEditingController(text: 'Wibble'); + addTearDown(controller1.dispose); final TextEditingController controller2 = TextEditingController(text: 'Wobble'); + addTearDown(controller2.dispose); TextEditingController currentController = controller1; late StateSetter setState; - final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node'); Widget builder() { return StatefulBuilder( builder: (BuildContext context, StateSetter setter) { @@ -3858,7 +3881,7 @@ void main() { ); }); - testWidgets('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -3898,7 +3921,7 @@ void main() { semantics.dispose(); }); - testWidgets('EditableText sets multi-line flag in semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText sets multi-line flag in semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -3958,11 +3981,10 @@ void main() { semantics.dispose(); }); - testWidgets('EditableText includes text as value in semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText includes text as value in semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const String value1 = 'EditableText content'; - controller.text = value1; await tester.pumpWidget( @@ -4008,7 +4030,7 @@ void main() { semantics.dispose(); }); - testWidgets('exposes correct cursor movement semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('exposes correct cursor movement semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); controller.text = 'test'; @@ -4091,7 +4113,7 @@ void main() { semantics.dispose(); }); - testWidgets('can move cursor with a11y means - character', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can move cursor with a11y means - character', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const bool doNotExtendSelection = false; @@ -4196,7 +4218,7 @@ void main() { semantics.dispose(); }); - testWidgets('can move cursor with a11y means - word', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can move cursor with a11y means - word', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const bool doNotExtendSelection = false; @@ -4309,7 +4331,7 @@ void main() { semantics.dispose(); }); - testWidgets('can extend selection with a11y means - character', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can extend selection with a11y means - character', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const bool extendSelection = true; const bool doNotExtendSelection = false; @@ -4425,7 +4447,7 @@ void main() { semantics.dispose(); }); - testWidgets('can extend selection with a11y means - word', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can extend selection with a11y means - word', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const bool extendSelection = true; const bool doNotExtendSelection = false; @@ -4539,7 +4561,7 @@ void main() { semantics.dispose(); }); - testWidgets('password fields have correct semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('password fields have correct semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); controller.text = 'super-secret-password!!1'; @@ -4594,7 +4616,7 @@ void main() { semantics.dispose(); }); - testWidgets('password fields become obscured with the right semantics when set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('password fields become obscured with the right semantics when set', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); const String originalText = 'super-secret-password!!1'; @@ -4709,7 +4731,7 @@ void main() { semantics.dispose(); }); - testWidgets('password fields can have their obscuring character customized', (WidgetTester tester) async { + testWidgetsWithLeakTracking('password fields can have their obscuring character customized', (WidgetTester tester) async { const String originalText = 'super-secret-password!!1'; controller.text = originalText; @@ -4730,7 +4752,7 @@ void main() { expect((findRenderEditable(tester).text! as TextSpan).text, expectedValue); }); - testWidgets('password briefly shows last character when entered on mobile', (WidgetTester tester) async { + testWidgetsWithLeakTracking('password briefly shows last character when entered on mobile', (WidgetTester tester) async { final bool debugDeterministicCursor = EditableText.debugDeterministicCursor; EditableText.debugDeterministicCursor = false; addTearDown(() { @@ -4784,7 +4806,7 @@ void main() { controls = MockTextSelectionControls(); }); - testWidgets('are exposed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('are exposed', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); controls.testCanCopy = false; @@ -4881,7 +4903,7 @@ void main() { semantics.dispose(); }); - testWidgets('can copy/cut/paste with a11y', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can copy/cut/paste with a11y', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); controls.testCanCopy = true; @@ -4954,10 +4976,11 @@ void main() { }); // Regression test for b/201218542. - testWidgets('copying with a11y works even when toolbar is hidden', (WidgetTester tester) async { + testWidgetsWithLeakTracking('copying with a11y works even when toolbar is hidden', (WidgetTester tester) async { Future testByControls(TextSelectionControls controls) async { final SemanticsTester semantics = SemanticsTester(tester); final TextEditingController controller = TextEditingController(text: 'ABCDEFG'); + addTearDown(controller.dispose); await tester.pumpWidget(MaterialApp( home: EditableText( @@ -4999,7 +5022,7 @@ void main() { }); }); - testWidgets('can set text with a11y', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can set text with a11y', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(MaterialApp( home: EditableText( @@ -5064,7 +5087,7 @@ void main() { semantics.dispose(); }); - testWidgets('allows customizing text style in subclasses', (WidgetTester tester) async { + testWidgetsWithLeakTracking('allows customizing text style in subclasses', (WidgetTester tester) async { controller.text = 'Hello World'; await tester.pumpWidget(MaterialApp( @@ -5081,9 +5104,8 @@ void main() { expect(render.text!.style!.fontStyle, FontStyle.italic); }); - testWidgets('onChanged callback only invoked on text changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onChanged callback only invoked on text changes', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/111651 . - final TextEditingController controller = TextEditingController(); int onChangedCount = 0; bool preventInput = false; final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) { @@ -5096,7 +5118,7 @@ void main() { controller: controller, backgroundCursorColor: Colors.red, cursorColor: Colors.red, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, onChanged: (String newString) { onChangedCount += 1; }, inputFormatters: [formatter], @@ -5127,20 +5149,19 @@ void main() { expect(onChangedCount , 2); }); - testWidgets('Formatters are skipped if text has not changed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Formatters are skipped if text has not changed', (WidgetTester tester) async { int called = 0; final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) { called += 1; return newValue; }); - final TextEditingController controller = TextEditingController(); final MediaQuery mediaQuery = MediaQuery( data: const MediaQueryData(), child: EditableText( controller: controller, backgroundCursorColor: Colors.red, cursorColor: Colors.red, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, inputFormatters: [ formatter, @@ -5171,7 +5192,7 @@ void main() { expect(called, 2); }); - testWidgets('default keyboardAppearance is respected', (WidgetTester tester) async { + testWidgetsWithLeakTracking('default keyboardAppearance is respected', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/22212. final List log = []; @@ -5180,7 +5201,6 @@ void main() { return null; }); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -5188,7 +5208,7 @@ void main() { textDirection: TextDirection.ltr, child: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -5203,14 +5223,13 @@ void main() { expect(((setClient.arguments as Iterable).last as Map)['keyboardAppearance'], 'Brightness.light'); }); - testWidgets('location of widget is sent on show keyboard', (WidgetTester tester) async { + testWidgetsWithLeakTracking('location of widget is sent on show keyboard', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -5218,7 +5237,7 @@ void main() { textDirection: TextDirection.ltr, child: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -5239,7 +5258,7 @@ void main() { ); }); - testWidgets('transform and size is reset when text connection opens', (WidgetTester tester) async { + testWidgetsWithLeakTracking('transform and size is reset when text connection opens', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); @@ -5247,7 +5266,13 @@ void main() { }); final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); + final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); + final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); controller1.text = 'Text1'; controller2.text = 'Text2'; @@ -5262,7 +5287,7 @@ void main() { EditableText( key: ValueKey(controller1.text), controller: controller1, - focusNode: FocusNode(), + focusNode: focusNode1, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -5271,7 +5296,7 @@ void main() { EditableText( key: ValueKey(controller2.text), controller: controller2, - focusNode: FocusNode(), + focusNode: focusNode2, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -5325,7 +5350,7 @@ void main() { ); }); - testWidgets('size and transform are sent when they change', (WidgetTester tester) async { + testWidgetsWithLeakTracking('size and transform are sent when they change', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); @@ -5368,7 +5393,7 @@ void main() { ); }); - testWidgets('selection rects are sent when they change', (WidgetTester tester) async { + testWidgetsWithLeakTracking('selection rects are sent when they change', (WidgetTester tester) async { addTearDown(tester.view.reset); // Ensure selection rects are sent on iPhone (using SE 3rd gen size) tester.view.physicalSize = const Size(750.0, 1334.0); @@ -5389,8 +5414,8 @@ void main() { return null; }); - final TextEditingController controller = TextEditingController(); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); controller.text = 'Text1'; Future pumpEditableText({ double? width, double? height, TextAlign textAlign = TextAlign.start }) async { @@ -5508,14 +5533,13 @@ void main() { // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('selection rects are not sent if scribbleEnabled is false', (WidgetTester tester) async { + testWidgetsWithLeakTracking('selection rects are not sent if scribbleEnabled is false', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); controller.text = 'Text1'; await tester.pumpWidget( @@ -5529,7 +5553,7 @@ void main() { EditableText( key: ValueKey(controller.text), controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -5548,7 +5572,7 @@ void main() { // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('selection rects sent even when character corners are outside of paintBounds', (WidgetTester tester) async { + testWidgetsWithLeakTracking('selection rects sent even when character corners are outside of paintBounds', (WidgetTester tester) async { final List> log = >[]; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) { if (methodCall.method == 'TextInput.setSelectionRects') { @@ -5565,8 +5589,8 @@ void main() { return null; }); - final TextEditingController controller = TextEditingController(); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); controller.text = 'Text1'; final GlobalKey editableTextKey = GlobalKey(); @@ -5606,7 +5630,9 @@ void main() { // Scroll so that the top of each character is above the top of the renderEditable // and the bottom of each character is below the bottom of the renderEditable. - editableTextKey.currentState!.renderEditable.offset = ViewportOffset.fixed(0.5); + final ViewportOffset offset = ViewportOffset.fixed(0.5); + addTearDown(offset.dispose); + editableTextKey.currentState!.renderEditable.offset = offset; await tester.showKeyboard(find.byType(EditableText)); // We should get all the rects. @@ -5622,21 +5648,20 @@ void main() { // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('text styling info is sent on show keyboard', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text styling info is sent on show keyboard', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), child: EditableText( textDirection: TextDirection.rtl, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: const TextStyle( fontSize: 20.0, fontFamily: 'Roboto', @@ -5662,21 +5687,20 @@ void main() { ); }); - testWidgets('text styling info is sent on show keyboard (bold override)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text styling info is sent on show keyboard (bold override)', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(boldText: true), child: EditableText( textDirection: TextDirection.rtl, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: const TextStyle( fontSize: 20.0, fontFamily: 'Roboto', @@ -5702,7 +5726,7 @@ void main() { ); }); - testWidgets('text styling info is sent on style update', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text styling info is sent on style update', (WidgetTester tester) async { final GlobalKey editableTextKey = GlobalKey(); late StateSetter setState; const TextStyle textStyle1 = TextStyle( @@ -5732,7 +5756,7 @@ void main() { backgroundCursorColor: Colors.grey, key: editableTextKey, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: currentTextStyle, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -5787,7 +5811,7 @@ void main() { child: EditableText( backgroundCursorColor: Colors.grey, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -5801,7 +5825,7 @@ void main() { ); } - testWidgets( + testWidgetsWithLeakTracking( 'called with proper coordinates', (WidgetTester tester) async { controller.value = TextEditingValue(text: 'a' * 50); @@ -5843,7 +5867,7 @@ void main() { }, ); - testWidgets( + testWidgetsWithLeakTracking( 'only send updates when necessary', (WidgetTester tester) async { controller.value = TextEditingValue(text: 'a' * 100); @@ -5861,7 +5885,7 @@ void main() { }, ); - testWidgets( + testWidgetsWithLeakTracking( 'not sent with selection', (WidgetTester tester) async { controller.value = TextEditingValue( @@ -5888,7 +5912,7 @@ void main() { child: EditableText( backgroundCursorColor: Colors.grey, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -5902,7 +5926,7 @@ void main() { ); } - testWidgets( + testWidgetsWithLeakTracking( 'called when the composing range changes', (WidgetTester tester) async { controller.value = TextEditingValue(text: 'a' * 100); @@ -5936,7 +5960,7 @@ void main() { }, ); - testWidgets( + testWidgetsWithLeakTracking( 'only send updates when necessary', (WidgetTester tester) async { controller.value = TextEditingValue(text: 'a' * 100, composing: const TextRange(start: 0, end: 10)); @@ -5954,7 +5978,7 @@ void main() { }, ); - testWidgets( + testWidgetsWithLeakTracking( 'zero matrix paint transform', (WidgetTester tester) async { controller.value = TextEditingValue(text: 'a' * 100, composing: const TextRange(start: 0, end: 10)); @@ -5976,7 +6000,7 @@ void main() { }); - testWidgets('custom keyboardAppearance is respected', (WidgetTester tester) async { + testWidgetsWithLeakTracking('custom keyboardAppearance is respected', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/22212. final List log = []; @@ -5985,7 +6009,6 @@ void main() { return null; }); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -5993,7 +6016,7 @@ void main() { textDirection: TextDirection.ltr, child: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -6009,15 +6032,12 @@ void main() { expect(((setClient.arguments as Iterable).last as Map)['keyboardAppearance'], 'Brightness.dark'); }); - testWidgets('Composing text is underlined and underline is cleared when losing focus', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController.fromValue( - const TextEditingValue( - text: 'text composing text', - selection: TextSelection.collapsed(offset: 14), - composing: TextRange(start: 5, end: 14), - ), + testWidgetsWithLeakTracking('Composing text is underlined and underline is cleared when losing focus', (WidgetTester tester) async { + controller.value = const TextEditingValue( + text: 'text composing text', + selection: TextSelection.collapsed(offset: 14), + composing: TextRange(start: 5, end: 14), ); - final FocusNode focusNode = FocusNode(debugLabel: 'Test Focus Node'); await tester.pumpWidget(MaterialApp( // So we can show overlays. home: EditableText( @@ -6057,9 +6077,8 @@ void main() { expect(renderEditable.text!.style!.decoration, isNull); }); - testWidgets('text selection toolbar visibility', (WidgetTester tester) async { - const String testText = 'hello \n world \n this \n is \n text'; - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('text selection toolbar visibility', (WidgetTester tester) async { + controller.text = 'hello \n world \n this \n is \n text'; await tester.pumpWidget(MaterialApp( home: Align( @@ -6070,7 +6089,7 @@ void main() { child: EditableText( showSelectionHandles: true, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -6126,10 +6145,9 @@ void main() { // toolbar. Until we change that, this test should remain skipped. }, skip: kIsWeb); // [intended] - testWidgets('text selection handle visibility', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text selection handle visibility', (WidgetTester tester) async { // Text with two separate words to select. - const String testText = 'XXXXX XXXXX'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'XXXXX XXXXX'; await tester.pumpWidget(MaterialApp( home: Align( @@ -6139,7 +6157,7 @@ void main() { child: EditableText( showSelectionHandles: true, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -6298,10 +6316,9 @@ void main() { // toolbar. Until we change that, this test should remain skipped. }, skip: kIsWeb); // [intended] - testWidgets('text selection handle visibility RTL', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text selection handle visibility RTL', (WidgetTester tester) async { // Text with two separate words to select. - const String testText = 'XXXXX XXXXX'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'XXXXX XXXXX'; await tester.pumpWidget(MaterialApp( home: Align( @@ -6311,7 +6328,7 @@ void main() { child: EditableText( controller: controller, showSelectionHandles: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -6368,7 +6385,7 @@ void main() { Future testTextEditing(WidgetTester tester, {required TargetPlatform targetPlatform}) async { final String targetPlatformString = targetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -6386,7 +6403,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -7367,7 +7384,7 @@ void main() { } } - testWidgets('keyboard text selection works (RawKeyEvent)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('keyboard text selection works (RawKeyEvent)', (WidgetTester tester) async { debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; await testTextEditing(tester, targetPlatform: defaultTargetPlatform); @@ -7377,7 +7394,7 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('keyboard text selection works (ui.KeyData then RawKeyEvent)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('keyboard text selection works (ui.KeyData then RawKeyEvent)', (WidgetTester tester) async { debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; await testTextEditing(tester, targetPlatform: defaultTargetPlatform); @@ -7387,11 +7404,11 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets( + testWidgetsWithLeakTracking( 'keyboard shortcuts respect read-only', (WidgetTester tester) async { final String platform = defaultTargetPlatform.name.toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: testText.length ~/2, @@ -7407,7 +7424,7 @@ void main() { readOnly: true, controller: controller, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -7566,10 +7583,10 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('home/end keys', (WidgetTester tester) async { + testWidgetsWithLeakTracking('home/end keys', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -7587,7 +7604,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -7712,11 +7729,11 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('home keys and wordwraps', (WidgetTester tester) async { + testWidgetsWithLeakTracking('home keys and wordwraps', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -7734,7 +7751,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -7868,11 +7885,11 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('end keys and wordwraps', (WidgetTester tester) async { + testWidgetsWithLeakTracking('end keys and wordwraps', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -7890,7 +7907,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8026,10 +8043,10 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('shift + home/end keys', (WidgetTester tester) async { + testWidgetsWithLeakTracking('shift + home/end keys', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8047,7 +8064,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8220,8 +8237,8 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('shift + home/end keys (Windows only)', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('shift + home/end keys (Windows only)', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8237,7 +8254,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8331,9 +8348,9 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.windows }) ); - testWidgets('home/end keys scrolling (Mac only)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('home/end keys scrolling (Mac only)', (WidgetTester tester) async { const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8349,7 +8366,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8392,11 +8409,11 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.macOS }) ); - testWidgets('shift + home keys and wordwraps', (WidgetTester tester) async { + testWidgetsWithLeakTracking('shift + home keys and wordwraps', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8414,7 +8431,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8577,11 +8594,11 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('shift + end keys and wordwraps', (WidgetTester tester) async { + testWidgetsWithLeakTracking('shift + end keys and wordwraps', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8599,7 +8616,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8764,9 +8781,9 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('shift + home/end keys to document boundary (Mac only)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('shift + home/end keys to document boundary (Mac only)', (WidgetTester tester) async { const String testText = 'Now is the time for all good people to come to the aid of their country. Now is the time for all good people to come to the aid of their country.'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8783,7 +8800,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8868,8 +8885,8 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.macOS }) ); - testWidgets('control + home/end keys (Windows only)', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('control + home/end keys (Windows only)', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8885,7 +8902,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -8933,8 +8950,8 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.windows }) ); - testWidgets('control + shift + home/end keys (Windows only)', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('control + shift + home/end keys (Windows only)', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -8950,7 +8967,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9020,14 +9037,15 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.windows }) ); - testWidgets('pageup/pagedown keys on Apple platforms', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('pageup/pagedown keys on Apple platforms', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, affinity: TextAffinity.upstream, ); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); const int lines = 2; await tester.pumpWidget(MaterialApp( home: Align( @@ -9041,7 +9059,7 @@ void main() { scrollController: scrollController, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.subtitle1!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9115,14 +9133,15 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets('pageup/pagedown keys in a one line field on Apple platforms', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('pageup/pagedown keys in a one line field on Apple platforms', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, affinity: TextAffinity.upstream, ); final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); await tester.pumpWidget(MaterialApp( home: Align( alignment: Alignment.topLeft, @@ -9134,7 +9153,7 @@ void main() { scrollController: scrollController, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.subtitle1!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9193,10 +9212,9 @@ void main() { ); // Regression test for https://github.com/flutter/flutter/issues/31287 - testWidgets('text selection handle visibility', (WidgetTester tester) async { + testWidgetsWithLeakTracking('text selection handle visibility', (WidgetTester tester) async { // Text with two separate words to select. - const String testText = 'XXXXX XXXXX'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'XXXXX XXXXX'; await tester.pumpWidget(MaterialApp( home: Align( @@ -9206,7 +9224,7 @@ void main() { child: EditableText( showSelectionHandles: true, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018(platform: TargetPlatform.iOS).black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9365,10 +9383,9 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }) ); - testWidgets("scrolling doesn't bounce", (WidgetTester tester) async { + testWidgetsWithLeakTracking("scrolling doesn't bounce", (WidgetTester tester) async { // 3 lines of text, where the last line overflows and requires scrolling. - const String testText = 'XXXXX\nXXXXX\nXXXXX'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'XXXXX\nXXXXX\nXXXXX'; await tester.pumpWidget(MaterialApp( home: Align( @@ -9379,7 +9396,7 @@ void main() { showSelectionHandles: true, maxLines: 2, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9415,11 +9432,13 @@ void main() { expect(scrollable.controller!.position.pixels, equals(renderEditable.maxScrollExtent)); }); - testWidgets('bringIntoView brings the caret into view when in a viewport', (WidgetTester tester) async { + testWidgetsWithLeakTracking('bringIntoView brings the caret into view when in a viewport', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/55547. - final TextEditingController controller = TextEditingController(text: testText * 20); + controller.text = testText * 20; final ScrollController editableScrollController = ScrollController(); + addTearDown(editableScrollController.dispose); final ScrollController outerController = ScrollController(); + addTearDown(outerController.dispose); await tester.pumpWidget(MaterialApp( home: Align( @@ -9433,7 +9452,7 @@ void main() { maxLines: null, controller: controller, scrollController: editableScrollController, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9457,9 +9476,10 @@ void main() { expect(editableScrollController.offset, 0); }); - testWidgets('bringIntoView does nothing if the physics prohibits implicit scrolling', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText * 20); + testWidgetsWithLeakTracking('bringIntoView does nothing if the physics prohibits implicit scrolling', (WidgetTester tester) async { + controller.text = testText * 20; final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); Future buildWithPhysics({ ScrollPhysics? physics }) async { await tester.pumpWidget(MaterialApp( @@ -9472,7 +9492,7 @@ void main() { maxLines: null, controller: controller, scrollController: scrollController, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9504,15 +9524,18 @@ void main() { expect(scrollController.offset, 0); }); - testWidgets('can change scroll controller', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can change scroll controller', (WidgetTester tester) async { + controller.text = 'A' * 1000; final _TestScrollController scrollController1 = _TestScrollController(); + addTearDown(scrollController1.dispose); final _TestScrollController scrollController2 = _TestScrollController(); + addTearDown(scrollController2.dispose); await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A' * 1000), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9528,8 +9551,8 @@ void main() { await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A' * 1000), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9545,8 +9568,8 @@ void main() { await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A' * 1000), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9561,8 +9584,8 @@ void main() { await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A' * 1000), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9575,15 +9598,15 @@ void main() { expect(scrollController2.attached, isTrue); }); - testWidgets('getLocalRectForCaret does not throw when it sees an infinite point', (WidgetTester tester) async { + testWidgetsWithLeakTracking('getLocalRectForCaret does not throw when it sees an infinite point', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SkipPainting( child: Transform( transform: Matrix4.zero(), child: EditableText( - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -9599,8 +9622,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('obscured multiline fields throw an exception', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('obscured multiline fields throw an exception', (WidgetTester tester) async { expect( () { EditableText( @@ -9631,33 +9653,32 @@ void main() { }); group('batch editing', () { - final TextEditingController controller = TextEditingController(text: testText); - final EditableText editableText = EditableText( - showSelectionHandles: true, - maxLines: 2, - controller: controller, - focusNode: FocusNode(), - cursorColor: Colors.red, - backgroundCursorColor: Colors.blue, - style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), - keyboardType: TextInputType.text, - ); - - final Widget widget = MediaQuery( - data: const MediaQueryData(), - child: Directionality( - textDirection: TextDirection.ltr, - child: editableText, - ), - ); + Widget buildWidget() { + return MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: EditableText( + showSelectionHandles: true, + maxLines: 2, + controller: controller, + focusNode: focusNode, + cursorColor: Colors.red, + backgroundCursorColor: Colors.blue, + style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), + keyboardType: TextInputType.text, + ), + ), + ); + } - testWidgets('batch editing works', (WidgetTester tester) async { - await tester.pumpWidget(widget); + testWidgetsWithLeakTracking('batch editing works', (WidgetTester tester) async { + await tester.pumpWidget(buildWidget()); // Connect. await tester.showKeyboard(find.byType(EditableText)); - final EditableTextState state = tester.state(find.byWidget(editableText)); + final EditableTextState state = tester.state(find.byType(EditableText)); state.updateEditingValue(const TextEditingValue(text: 'remote value')); tester.testTextInput.log.clear(); @@ -9690,13 +9711,13 @@ void main() { ); }); - testWidgets('batch edits need to be nested properly', (WidgetTester tester) async { - await tester.pumpWidget(widget); + testWidgetsWithLeakTracking('batch edits need to be nested properly', (WidgetTester tester) async { + await tester.pumpWidget(buildWidget()); // Connect. await tester.showKeyboard(find.byType(EditableText)); - final EditableTextState state = tester.state(find.byWidget(editableText)); + final EditableTextState state = tester.state(find.byType(EditableText)); state.updateEditingValue(const TextEditingValue(text: 'remote value')); tester.testTextInput.log.clear(); @@ -9710,13 +9731,13 @@ void main() { expect(errorString, contains('Unbalanced call to endBatchEdit')); }); - testWidgets('catch unfinished batch edits on disposal', (WidgetTester tester) async { - await tester.pumpWidget(widget); + testWidgetsWithLeakTracking('catch unfinished batch edits on disposal', (WidgetTester tester) async { + await tester.pumpWidget(buildWidget()); // Connect. await tester.showKeyboard(find.byType(EditableText)); - final EditableTextState state = tester.state(find.byWidget(editableText)); + final EditableTextState state = tester.state(find.byType(EditableText)); state.updateEditingValue(const TextEditingValue(text: 'remote value')); tester.testTextInput.log.clear(); @@ -9729,12 +9750,12 @@ void main() { }); group('EditableText does not send editing values more than once', () { - Widget boilerplate(TextEditingController controller) { + Widget boilerplate() { final EditableText editableText = EditableText( showSelectionHandles: true, maxLines: 2, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -9760,9 +9781,9 @@ void main() { ); } - testWidgets('input from text input plugin', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); - await tester.pumpWidget(boilerplate(controller)); + testWidgetsWithLeakTracking('input from text input plugin', (WidgetTester tester) async { + controller.text = testText; + await tester.pumpWidget(boilerplate()); // Connect. await tester.showKeyboard(find.byType(EditableText)); @@ -9790,9 +9811,9 @@ void main() { expect(tester.testTextInput.log, isEmpty); }); - testWidgets('input from text selection menu', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); - await tester.pumpWidget(boilerplate(controller)); + testWidgetsWithLeakTracking('input from text selection menu', (WidgetTester tester) async { + controller.text = testText; + await tester.pumpWidget(boilerplate()); // Connect. await tester.showKeyboard(find.byType(EditableText)); @@ -9815,9 +9836,9 @@ void main() { tester.testTextInput.log.clear(); }); - testWidgets('input from controller', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); - await tester.pumpWidget(boilerplate(controller)); + testWidgetsWithLeakTracking('input from controller', (WidgetTester tester) async { + controller.text = testText; + await tester.pumpWidget(boilerplate()); // Connect. await tester.showKeyboard(find.byType(EditableText)); @@ -9832,8 +9853,7 @@ void main() { expect(updates, [collapsedAtEnd('remoteremoteremote listener')]); }); - testWidgets('input from changing controller', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('input from changing controller', (WidgetTester tester) async { Widget build({ TextEditingController? textEditingController }) { return MediaQuery( data: const MediaQueryData(), @@ -9843,7 +9863,7 @@ void main() { showSelectionHandles: true, maxLines: 2, controller: textEditingController ?? controller, - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -9859,7 +9879,9 @@ void main() { // Connect. await tester.showKeyboard(find.byType(EditableText)); tester.testTextInput.log.clear(); - await tester.pumpWidget(build(textEditingController: TextEditingController(text: 'new text'))); + final TextEditingController controller1 = TextEditingController(text: 'new text'); + addTearDown(controller1.dispose); + await tester.pumpWidget(build(textEditingController: controller1)); List updates = tester.testTextInput.log .where((MethodCall call) => call.method == 'TextInput.setEditingState') @@ -9869,7 +9891,9 @@ void main() { expect(updates, const [TextEditingValue(text: 'new text')]); tester.testTextInput.log.clear(); - await tester.pumpWidget(build(textEditingController: TextEditingController(text: 'new new text'))); + final TextEditingController controller2 = TextEditingController(text: 'new new text'); + addTearDown(controller2.dispose); + await tester.pumpWidget(build(textEditingController: controller2)); updates = tester.testTextInput.log .where((MethodCall call) => call.method == 'TextInput.setEditingState') @@ -9880,14 +9904,13 @@ void main() { }); }); - testWidgets('input imm channel calls are ordered correctly', (WidgetTester tester) async { - const String testText = 'flutter is the best!'; - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('input imm channel calls are ordered correctly', (WidgetTester tester) async { + controller.text = 'flutter is the best!'; final EditableText et = EditableText( showSelectionHandles: true, maxLines: 2, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -9928,26 +9951,34 @@ void main() { ); }); - testWidgets( + testWidgetsWithLeakTracking( 'keyboard is requested after setEditingState after switching to a new text field', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/68571. + final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); + final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); final EditableText editableText1 = EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller1, + focusNode: focusNode1, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), keyboardType: TextInputType.text, ); + final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); + final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); final EditableText editableText2 = EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller2, + focusNode: focusNode2, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -9990,15 +10021,18 @@ void main() { ); }); - testWidgets( + testWidgetsWithLeakTracking( 'Autofill does not request focus', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/91354 . + final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); final EditableText editableText1 = EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController(), + controller: controller1, focusNode: focusNode1, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, @@ -10006,11 +10040,14 @@ void main() { keyboardType: TextInputType.text, ); + final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); final EditableText editableText2 = EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController(), + controller: controller2, focusNode: focusNode2, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, @@ -10041,15 +10078,14 @@ void main() { expect(focusNode2.hasFocus, isFalse); }); - testWidgets('setEditingState is not called when text changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('setEditingState is not called when text changes', (WidgetTester tester) async { // We shouldn't get a message here because this change is owned by the platform side. - const String testText = 'flutter is the best!'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'flutter is the best!'; final EditableText et = EditableText( showSelectionHandles: true, maxLines: 2, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -10090,15 +10126,14 @@ void main() { expect(tester.testTextInput.editingState!['text'], 'flutter is the best!'); }); - testWidgets('setEditingState is called when text changes on controller', (WidgetTester tester) async { + testWidgetsWithLeakTracking('setEditingState is called when text changes on controller', (WidgetTester tester) async { // We should get a message here because this change is owned by the framework side. - const String testText = 'flutter is the best!'; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = 'flutter is the best!'; final EditableText et = EditableText( showSelectionHandles: true, maxLines: 2, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -10140,7 +10175,7 @@ void main() { expect(tester.testTextInput.editingState!['text'], 'flutter is the best!...'); }); - testWidgets('Synchronous test of local and remote editing values', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Synchronous test of local and remote editing values', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/65059 final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { @@ -10153,10 +10188,8 @@ void main() { } return newValue; }); - final TextEditingController controller = TextEditingController(); late StateSetter setState; - final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node'); Widget builder() { return StatefulBuilder( builder: (BuildContext context, StateSetter setter) { @@ -10271,7 +10304,7 @@ void main() { ); }); - testWidgets('Send text input state to engine when the input formatter rejects user input', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Send text input state to engine when the input formatter rejects user input', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/67828 final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { @@ -10281,9 +10314,7 @@ void main() { final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) { return collapsedAtEnd('Flutter is the best!'); }); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node'); Widget builder() { return StatefulBuilder( builder: (BuildContext context, StateSetter setter) { @@ -10346,16 +10377,14 @@ void main() { ))); }); - testWidgets('Repeatedly receiving [TextEditingValue] will not trigger a keyboard request', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Repeatedly receiving [TextEditingValue] will not trigger a keyboard request', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/66036 final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node'); Widget builder() { return StatefulBuilder( builder: (BuildContext context, StateSetter setter) { @@ -10433,8 +10462,7 @@ void main() { }); group('TextEditingController', () { - testWidgets('TextEditingController.text set to empty string clears field', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('TextEditingController.text set to empty string clears field', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: MediaQuery( @@ -10468,16 +10496,14 @@ void main() { expect(find.text('...'), findsNothing); }); - testWidgets('TextEditingController.clear() behavior test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('TextEditingController.clear() behavior test', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/66316 final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); return null; }); - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node'); Widget builder() { return StatefulBuilder( builder: (BuildContext context, StateSetter setter) { @@ -10543,8 +10569,9 @@ void main() { ); }); - testWidgets('TextEditingController.buildTextSpan receives build context', (WidgetTester tester) async { + testWidgetsWithLeakTracking('TextEditingController.buildTextSpan receives build context', (WidgetTester tester) async { final _AccentColorTextEditingController controller = _AccentColorTextEditingController('a'); + addTearDown(controller.dispose); const Color color = Color.fromARGB(255, 1, 2, 3); final ThemeData lightTheme = ThemeData.light(); await tester.pumpWidget(MaterialApp( @@ -10553,7 +10580,7 @@ void main() { ), home: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -10565,9 +10592,8 @@ void main() { expect(textSpan.style!.color, color); }); - testWidgets('controller listener changes value', (WidgetTester tester) async { + testWidgetsWithLeakTracking('controller listener changes value', (WidgetTester tester) async { const double maxValue = 5.5555; - final TextEditingController controller = TextEditingController(); controller.addListener(() { final double value = double.tryParse(controller.text.trim()) ?? .0; @@ -10601,8 +10627,8 @@ void main() { }); }); - testWidgets('autofocus:true on first frame does not throw', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('autofocus:true on first frame does not throw', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -10615,7 +10641,7 @@ void main() { controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -10632,7 +10658,7 @@ void main() { expect(exception, isNull); }); - testWidgets('updateEditingValue filters multiple calls from formatter', (WidgetTester tester) async { + testWidgetsWithLeakTracking('updateEditingValue filters multiple calls from formatter', (WidgetTester tester) async { final MockTextFormatter formatter = MockTextFormatter(); await tester.pumpWidget( MediaQuery( @@ -10704,7 +10730,7 @@ void main() { expect(formatter.log, referenceLog); }); - testWidgets('formatter logic handles repeat filtering', (WidgetTester tester) async { + testWidgetsWithLeakTracking('formatter logic handles repeat filtering', (WidgetTester tester) async { final MockTextFormatter formatter = MockTextFormatter(); await tester.pumpWidget( MediaQuery( @@ -10785,7 +10811,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/53612 - testWidgets('formatter logic handles initial repeat edge case', (WidgetTester tester) async { + testWidgetsWithLeakTracking('formatter logic handles initial repeat edge case', (WidgetTester tester) async { final MockTextFormatter formatter = MockTextFormatter(); await tester.pumpWidget( MediaQuery( @@ -10827,7 +10853,7 @@ void main() { expect(formatter.lastOldValue.text, 'test'); }); - testWidgets('EditableText changes mouse cursor when hovered', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText changes mouse cursor when hovered', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -10884,13 +10910,13 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); }); - testWidgets('Can access characters on editing string', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can access characters on editing string', (WidgetTester tester) async { late int charactersLength; final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, selectionControls: materialTextSelectionControls, @@ -10910,7 +10936,7 @@ void main() { expect(charactersLength, 1); }); - testWidgets('EditableText can set and update clipBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText can set and update clipBehavior', (WidgetTester tester) async { await tester.pumpWidget(MediaQuery( data: const MediaQueryData(), child: Directionality( @@ -10952,7 +10978,7 @@ void main() { expect(renderObject.clipBehavior, equals(Clip.antiAlias)); }); - testWidgets('EditableText inherits DefaultTextHeightBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText inherits DefaultTextHeightBehavior', (WidgetTester tester) async { const TextHeightBehavior customTextHeightBehavior = TextHeightBehavior( applyHeightToFirstAscent: false, ); @@ -10980,7 +11006,7 @@ void main() { expect(renderObject.textHeightBehavior, equals(customTextHeightBehavior)); }); - testWidgets('EditableText defaultTextHeightBehavior is used over inherited widget', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText defaultTextHeightBehavior is used over inherited widget', (WidgetTester tester) async { const TextHeightBehavior inheritedTextHeightBehavior = TextHeightBehavior( applyHeightToFirstAscent: false, ); @@ -11018,7 +11044,9 @@ void main() { void expectToAssert(TextEditingValue value, bool shouldAssert) { dynamic initException; dynamic updateException; - controller = TextEditingController(); + + TextEditingController controller = TextEditingController(); + addTearDown(controller.dispose); try { controller = TextEditingController.fromValue(value); } catch (e) { @@ -11026,6 +11054,7 @@ void main() { } controller = TextEditingController(); + addTearDown(controller.dispose); try { controller.value = value; } catch (e) { @@ -11042,7 +11071,7 @@ void main() { expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: -1, end: 9)), false); }); - testWidgets('Preserves composing range if cursor moves within that range', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Preserves composing range if cursor moves within that range', (WidgetTester tester) async { final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, @@ -11064,7 +11093,7 @@ void main() { expect(state.currentTextEditingValue.composing, const TextRange(start: 4, end: 12)); }); - testWidgets('Clears composing range if cursor moves outside that range', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Clears composing range if cursor moves outside that range', (WidgetTester tester) async { final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, @@ -11105,7 +11134,7 @@ void main() { expect(state.currentTextEditingValue.composing, TextRange.empty); }); - testWidgets('Clears composing range if cursor moves outside that range - case two', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Clears composing range if cursor moves outside that range - case two', (WidgetTester tester) async { final Widget widget = MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, @@ -11187,7 +11216,7 @@ void main() { } // Regression test for https://github.com/flutter/flutter/issues/65374. - testWidgets('will not cause crash while the TextEditingValue is composing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('will not cause crash while the TextEditingValue is composing', (WidgetTester tester) async { await setupWidget( tester, LengthLimitingTextInputFormatter( @@ -11213,7 +11242,7 @@ void main() { expect(state.currentTextEditingValue.composing, TextRange.empty); }); - testWidgets('handles composing text correctly, continued', (WidgetTester tester) async { + testWidgetsWithLeakTracking('handles composing text correctly, continued', (WidgetTester tester) async { await setupWidget( tester, LengthLimitingTextInputFormatter( @@ -11245,7 +11274,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/68086. - testWidgets('enforced composing truncated', (WidgetTester tester) async { + testWidgetsWithLeakTracking('enforced composing truncated', (WidgetTester tester) async { await setupWidget( tester, LengthLimitingTextInputFormatter( @@ -11283,7 +11312,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/68086. - testWidgets('default truncate behaviors with different platforms', (WidgetTester tester) async { + testWidgetsWithLeakTracking('default truncate behaviors with different platforms', (WidgetTester tester) async { await setupWidget(tester, LengthLimitingTextInputFormatter(maxLength)); final EditableTextState state = tester.state(find.byType(EditableText)); @@ -11323,7 +11352,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/68086. - testWidgets("composing range removed if it's overflowed the truncated value's length", (WidgetTester tester) async { + testWidgetsWithLeakTracking("composing range removed if it's overflowed the truncated value's length", (WidgetTester tester) async { await setupWidget( tester, LengthLimitingTextInputFormatter( @@ -11352,7 +11381,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/68086. - testWidgets('composing range removed with different platforms', (WidgetTester tester) async { + testWidgetsWithLeakTracking('composing range removed with different platforms', (WidgetTester tester) async { await setupWidget(tester, LengthLimitingTextInputFormatter(maxLength)); final EditableTextState state = tester.state(find.byType(EditableText)); @@ -11383,7 +11412,7 @@ void main() { } }); - testWidgets("composing range handled correctly when it's overflowed", (WidgetTester tester) async { + testWidgetsWithLeakTracking("composing range handled correctly when it's overflowed", (WidgetTester tester) async { const String string = '👨‍👩‍👦0123456'; await setupWidget(tester, LengthLimitingTextInputFormatter(maxLength)); @@ -11404,7 +11433,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/68086. - testWidgets('typing in the middle with different platforms.', (WidgetTester tester) async { + testWidgetsWithLeakTracking('typing in the middle with different platforms.', (WidgetTester tester) async { await setupWidget(tester, LengthLimitingTextInputFormatter(maxLength)); final EditableTextState state = tester.state(find.byType(EditableText)); @@ -11446,15 +11475,15 @@ void main() { group('callback errors', () { const String errorText = 'Test EditableText callback error'; - testWidgets('onSelectionChanged can throw errors', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onSelectionChanged can throw errors', (WidgetTester tester) async { + controller.text = 'flutter is the best!'; + await tester.pumpWidget(MaterialApp( home: EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController( - text: 'flutter is the best!', - ), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -11473,15 +11502,15 @@ void main() { expect(error.toString(), contains(errorText)); }); - testWidgets('onChanged can throw errors', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onChanged can throw errors', (WidgetTester tester) async { + controller.text = 'flutter is the best!'; + await tester.pumpWidget(MaterialApp( home: EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController( - text: 'flutter is the best!', - ), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -11499,15 +11528,15 @@ void main() { expect(error.toString(), contains(errorText)); }); - testWidgets('onEditingComplete can throw errors', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onEditingComplete can throw errors', (WidgetTester tester) async { + controller.text = 'flutter is the best!'; + await tester.pumpWidget(MaterialApp( home: EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController( - text: 'flutter is the best!', - ), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -11530,15 +11559,15 @@ void main() { expect(error.toString(), contains(errorText)); }); - testWidgets('onSubmitted can throw errors', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onSubmitted can throw errors', (WidgetTester tester) async { + controller.text = 'flutter is the best!'; + await tester.pumpWidget(MaterialApp( home: EditableText( showSelectionHandles: true, maxLines: 2, - controller: TextEditingController( - text: 'flutter is the best!', - ), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -11561,20 +11590,19 @@ void main() { expect(error.toString(), contains(errorText)); }); - testWidgets('input formatters can throw errors', (WidgetTester tester) async { + testWidgetsWithLeakTracking('input formatters can throw errors', (WidgetTester tester) async { final TextInputFormatter badFormatter = TextInputFormatter.withFunction( (TextEditingValue oldValue, TextEditingValue newValue) => throw FlutterError(errorText), ); - final TextEditingController controller = TextEditingController( - text: 'flutter is the best!', - ); + controller.text = 'flutter is the best!'; + await tester.pumpWidget(MaterialApp( home: EditableText( showSelectionHandles: true, maxLines: 2, controller: controller, inputFormatters: [badFormatter], - focusNode: FocusNode(), + focusNode: focusNode, cursorColor: Colors.red, backgroundCursorColor: Colors.blue, style: Typography.material2018().black.titleMedium!.copyWith(fontFamily: 'Roboto'), @@ -11596,8 +11624,10 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/72400. - testWidgets("delete doesn't cause crash when selection is -1,-1", (WidgetTester tester) async { + testWidgetsWithLeakTracking("delete doesn't cause crash when selection is -1,-1", (WidgetTester tester) async { final UnsettableController unsettableController = UnsettableController(); + addTearDown(unsettableController.dispose); + await tester.pumpWidget( MediaQuery( data: const MediaQueryData(), @@ -11629,7 +11659,7 @@ void main() { expect(tester.takeException(), null); }); - testWidgets('can change behavior by overriding text editing shortcuts', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can change behavior by overriding text editing shortcuts', (WidgetTester tester) async { const Map testShortcuts = { SingleActivator(LogicalKeyboardKey.arrowLeft): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true), SingleActivator(LogicalKeyboardKey.keyX, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true), @@ -11637,7 +11667,7 @@ void main() { SingleActivator(LogicalKeyboardKey.keyV, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true), SingleActivator(LogicalKeyboardKey.keyA, control: true): ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true), }; - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -11694,8 +11724,8 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, skip: kIsWeb); // [intended] - testWidgets('navigating by word', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: 'word word word'); + testWidgetsWithLeakTracking('navigating by word', (WidgetTester tester) async { + controller.text = 'word word word'; // word wo|rd| word controller.selection = const TextSelection( baseOffset: 7, @@ -11831,9 +11861,8 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('navigating multiline text', (WidgetTester tester) async { - const String multilineText = 'word word word\nword word\nword'; // 15 + 10 + 4; - final TextEditingController controller = TextEditingController(text: multilineText); + testWidgetsWithLeakTracking('navigating multiline text', (WidgetTester tester) async { + controller.text = 'word word word\nword word\nword'; // 15 + 10 + 4; // wo|rd wo|rd controller.selection = const TextSelection( baseOffset: 17, @@ -11990,9 +12019,8 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets("Mac's expand by line behavior on multiple lines", (WidgetTester tester) async { - const String multilineText = 'word word word\nword word\nword'; // 15 + 10 + 4; - final TextEditingController controller = TextEditingController(text: multilineText); + testWidgetsWithLeakTracking("Mac's expand by line behavior on multiple lines", (WidgetTester tester) async { + controller.text = 'word word word\nword word\nword'; // 15 + 10 + 4; // word word word // wo|rd word // w|ord @@ -12092,9 +12120,8 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.macOS }) ); - testWidgets("Mac's expand extent position", (WidgetTester tester) async { - const String testText = 'Now is the time for all good people to come to the aid of their country'; - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking("Mac's expand extent position", (WidgetTester tester) async { + controller.text = 'Now is the time for all good people to come to the aid of their country'; // Start the selection in the middle somewhere. controller.selection = const TextSelection.collapsed(offset: 10); await tester.pumpWidget(MaterialApp( @@ -12326,8 +12353,8 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.macOS }) ); - testWidgets('expanding selection to start/end single line', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: 'word word word'); + testWidgetsWithLeakTracking('expanding selection to start/end single line', (WidgetTester tester) async { + controller.text = 'word word word'; // word wo|rd| word controller.selection = const TextSelection( baseOffset: 7, @@ -12413,8 +12440,8 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.macOS }) ); - testWidgets('can change text editing behavior by overriding actions', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: testText); + testWidgetsWithLeakTracking('can change text editing behavior by overriding actions', (WidgetTester tester) async { + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -12459,10 +12486,8 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, skip: kIsWeb); // [intended] - testWidgets('ignore key event from web platform', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController( - text: 'test\ntest', - ); + testWidgetsWithLeakTracking('ignore key event from web platform', (WidgetTester tester) async { + controller.text = 'test\ntest'; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -12515,7 +12540,7 @@ void main() { } }, variant: KeySimulatorTransitModeVariant.all()); - testWidgets('the toolbar is disposed when selection changes and there is no selectionControls', (WidgetTester tester) async { + testWidgetsWithLeakTracking('the toolbar is disposed when selection changes and there is no selectionControls', (WidgetTester tester) async { late StateSetter setState; bool enableInteractiveSelection = true; await tester.pumpWidget( @@ -12581,13 +12606,14 @@ void main() { // On web, using keyboard for selection is handled by the browser. }, skip: kIsWeb); // [intended] - testWidgets('EditableText does not leak animation controllers', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('EditableText does not leak animation controllers', (WidgetTester tester) async { + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( autofocus: true, - controller: TextEditingController(text: 'A'), + controller: controller, focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, @@ -12617,12 +12643,12 @@ void main() { expect(tester.hasRunningAnimations, isFalse); }); - testWidgets('Floating cursor affinity', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Floating cursor affinity', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; - final FocusNode focusNode = FocusNode(); final GlobalKey key = GlobalKey(); // Set it up so that there will be word-wrap. - final TextEditingController controller = TextEditingController(text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz'); + controller.text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz'; + await tester.pumpWidget( MaterialApp( home: Center( @@ -12679,16 +12705,20 @@ void main() { )); EditableText.debugDeterministicCursor = false; - }); + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 2}, + )); -testWidgets('Floating cursor ending with selection', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Floating cursor ending with selection', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; - final FocusNode focusNode = FocusNode(); final GlobalKey key = GlobalKey(); SelectionChangedCause? lastSelectionChangedCause; - - final TextEditingController controller = TextEditingController(text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\n1234567890'); + controller.text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\n1234567890'; controller.selection = const TextSelection.collapsed(offset: 0); + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -12860,8 +12890,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async lastSelectionChangedCause = null; EditableText.debugDeterministicCursor = false; - }); - + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134386 + notDisposedAllowList: {'LeaderLayer': 8}, + )); group('Selection changed scroll into view', () { final String text = List.generate(64, (int index) => index).join('\n'); @@ -12869,6 +12903,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async final ScrollController scrollController = ScrollController(); late double maxScrollExtent; + tearDownAll(() { + controller.dispose(); + scrollController.dispose(); + }); + Future resetSelectionAndScrollOffset(WidgetTester tester, {required bool setMaxScrollExtent}) async { controller.value = controller.value.copyWith( text: text, @@ -12882,7 +12921,6 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(scrollController.offset, targetOffset); } - Future pumpLongScrollableText(WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -12913,7 +12951,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async return key.currentState!; } - testWidgets('SelectAll toolbar action will not set max scroll on designated platforms', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SelectAll toolbar action will not set max scroll on designated platforms', (WidgetTester tester) async { final TextSelectionDelegate textSelectionDelegate = await pumpLongScrollableText(tester); await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: false); @@ -12922,7 +12960,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(scrollController.offset, 0.0); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('Selection will be scrolled into view with SelectionChangedCause', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Selection will be scrolled into view with SelectionChangedCause', (WidgetTester tester) async { final TextSelectionDelegate textSelectionDelegate = await pumpLongScrollableText(tester); // Cut @@ -12971,13 +13009,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS, TargetPlatform.macOS })); }); - testWidgets('Should not scroll on paste if caret already visible', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Should not scroll on paste if caret already visible', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/96658. final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController( - text: 'Lorem ipsum please paste here: \n${".\n" * 50}', - ); - final FocusNode focusNode = FocusNode(); + addTearDown(scrollController.dispose); + controller.text = 'Lorem ipsum please paste here: \n${".\n" * 50}'; await tester.pumpWidget( MaterialApp( @@ -13015,13 +13051,14 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(scrollController.offset, 0.0); }); - testWidgets('Autofill enabled by default', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('Autofill enabled by default', (WidgetTester tester) async { + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( autofocus: true, - controller: TextEditingController(text: 'A'), + controller: controller, focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, @@ -13038,13 +13075,14 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets('Autofill can be disabled', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('Autofill can be disabled', (WidgetTester tester) async { + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( autofocus: true, - controller: TextEditingController(text: 'A'), + controller: controller, focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, @@ -13078,11 +13116,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async Future sendUndo(WidgetTester tester) => sendUndoRedo(tester); Future sendRedo(WidgetTester tester) => sendUndoRedo(tester, true); - Widget boilerplate(TextEditingController controller, [FocusNode? focusNode]) { + Widget boilerplate() { return MaterialApp( home: EditableText( controller: controller, - focusNode: focusNode ?? FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -13122,9 +13160,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async selection: TextSelection.collapsed(offset: textAC.length), ); - testWidgets('Should have no effect on an empty and non-focused field', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - await tester.pumpWidget(boilerplate(controller)); + testWidgetsWithLeakTracking('Should have no effect on an empty and non-focused field', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); expect(controller.value, TextEditingValue.empty); // Undo/redo have no effect on an empty field that has never been edited. @@ -13138,10 +13175,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('Should have no effect on an empty and focused field', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + testWidgetsWithLeakTracking('Should have no effect on an empty and focused field', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); await waitForThrottling(tester); expect(controller.value, TextEditingValue.empty); @@ -13149,31 +13184,29 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // state saved in text editing history. focusNode.requestFocus(); await tester.pump(); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); await waitForThrottling(tester); // Undo/redo should have no effect. The field is focused and the value has // changed, but the text remains empty. await sendUndo(tester); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); await sendRedo(tester); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('Can undo/redo a single insertion', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + testWidgetsWithLeakTracking('Can undo/redo a single insertion', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); // Focus the field and wait for throttling delay to get the initial // state saved in text editing history. focusNode.requestFocus(); await tester.pump(); await waitForThrottling(tester); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); // First insertion. await tester.enterText(find.byType(EditableText), textA); @@ -13203,17 +13236,15 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('Can undo/redo multiple insertions', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + testWidgetsWithLeakTracking('Can undo/redo multiple insertions', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); // Focus the field and wait for throttling delay to get the initial // state saved in text editing history. focusNode.requestFocus(); await tester.pump(); await waitForThrottling(tester); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); // First insertion. await tester.enterText(find.byType(EditableText), textA); @@ -13247,17 +13278,15 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // Regression test for https://github.com/flutter/flutter/issues/120794. // This is only reproducible on Android platform because it is the only // platform where composing changes are saved in the editing history. - testWidgets('Can undo as intented when adding a delay between undos', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + testWidgetsWithLeakTracking('Can undo as intented when adding a delay between undos', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); // Focus the field and wait for throttling delay to get the initial // state saved in text editing history. focusNode.requestFocus(); await tester.pump(); await waitForThrottling(tester); - expect(controller.value, emptyTextCollapsed); + expect(controller.value, emptyTextCollapsed); final EditableTextState state = tester.state(find.byType(EditableText)); @@ -13303,11 +13332,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] // Regression test for https://github.com/flutter/flutter/issues/120194. - testWidgets('Cursor does not jump after undo', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor does not jump after undo', (WidgetTester tester) async { // Initialize the controller with a non empty text. - final TextEditingController controller = TextEditingController(text: textA); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + controller.text = textA; + await tester.pumpWidget(boilerplate()); // Focus the field and wait for throttling delay to get the initial // state saved in text editing history. @@ -13328,11 +13356,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('Initial value is recorded when an undo is received just after getting the focus', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Initial value is recorded when an undo is received just after getting the focus', (WidgetTester tester) async { // Initialize the controller with a non empty text. - final TextEditingController controller = TextEditingController(text: textA); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + controller.text = textA; + await tester.pumpWidget(boilerplate()); // Focus the field and do not wait for throttling delay before calling undo. focusNode.requestFocus(); @@ -13354,10 +13381,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('Can make changes in the middle of the history', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(boilerplate(controller, focusNode)); + testWidgetsWithLeakTracking('Can make changes in the middle of the history', (WidgetTester tester) async { + await tester.pumpWidget(boilerplate()); // Focus the field and wait for throttling delay to get the initial // state saved in text editing history. @@ -13408,9 +13433,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('inside EditableText, duplicate changes', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('inside EditableText, duplicate changes', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -13539,14 +13562,13 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('inside EditableText, autofocus', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); + testWidgetsWithLeakTracking('inside EditableText, autofocus', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( autofocus: true, controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: textStyle, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -13612,9 +13634,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('does not save composing changes (except Android)', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('does not save composing changes (except Android)', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -13773,9 +13793,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.android }), skip: kIsWeb); // [intended] - testWidgets('does save composing changes on Android', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('does save composing changes on Android', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -14007,9 +14025,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // On web, these keyboard shortcuts are handled by the browser. }, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] - testWidgets('saves right up to composing change even when throttled', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(); - final FocusNode focusNode = FocusNode(); + testWidgetsWithLeakTracking('saves right up to composing change even when throttled', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -14209,9 +14225,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] }); - testWidgets('pasting with the keyboard collapses the selection and places it after the pasted content', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pasting with the keyboard collapses the selection and places it after the pasted content', (WidgetTester tester) async { Future testPasteSelection(WidgetTester tester, _VoidFutureCallback paste) async { final TextEditingController controller = TextEditingController(); + addTearDown(controller.dispose); + await tester.pumpWidget( MaterialApp( home: EditableText( @@ -14344,7 +14362,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, skip: kIsWeb); // [intended] // Regression test for https://github.com/flutter/flutter/issues/98322. - testWidgets('EditableText consumes ActivateIntent and ButtonActivateIntent', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText consumes ActivateIntent and ButtonActivateIntent', (WidgetTester tester) async { bool receivedIntent = false; await tester.pumpWidget( MaterialApp( @@ -14386,8 +14404,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }); // Regression test for https://github.com/flutter/flutter/issues/100585. - testWidgets('can paste and remove field', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: 'text'); + testWidgetsWithLeakTracking('can paste and remove field', (WidgetTester tester) async { + controller.text = 'text'; late StateSetter setState; bool showField = true; final _CustomTextSelectionControls controls = _CustomTextSelectionControls( @@ -14436,8 +14454,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, skip: kIsWeb); // [intended] // Regression test for https://github.com/flutter/flutter/issues/100585. - testWidgets('can cut and remove field', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController(text: 'text'); + testWidgetsWithLeakTracking('can cut and remove field', (WidgetTester tester) async { + controller.text = 'text'; late StateSetter setState; bool showField = true; final _CustomTextSelectionControls controls = _CustomTextSelectionControls( @@ -14487,10 +14505,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }, skip: kIsWeb); // [intended] group('Mac document shortcuts', () { - testWidgets('ctrl-A/E', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ctrl-A/E', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -14506,7 +14524,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14568,10 +14586,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets('ctrl-F/B', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ctrl-F/B', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -14587,7 +14605,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14634,10 +14652,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets('ctrl-N/P', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ctrl-N/P', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -14653,7 +14671,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14713,11 +14731,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async await tester.pump(); } - testWidgets('with normal characters', (WidgetTester tester) async { + testWidgetsWithLeakTracking('with normal characters', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController(text: testText); + controller.text = testText; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -14733,7 +14751,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14808,15 +14826,13 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), ); - testWidgets('with extended grapheme clusters', (WidgetTester tester) async { + testWidgetsWithLeakTracking('with extended grapheme clusters', (WidgetTester tester) async { final String targetPlatformString = defaultTargetPlatform.toString(); final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); - final TextEditingController controller = TextEditingController( - // One extended grapheme cluster of length 8 and one surrogate pair of - // length 2. - text: '👨‍👩‍👦😆', - ); + // One extended grapheme cluster of length 8 and one surrogate pair of + // length 2. + controller.text = '👨‍👩‍👦😆'; controller.selection = const TextSelection( baseOffset: 0, extentOffset: 0, @@ -14832,7 +14848,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14892,7 +14908,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets('macOS selectors work', (WidgetTester tester) async { + testWidgetsWithLeakTracking('macOS selectors work', (WidgetTester tester) async { controller.text = 'test\nline2'; controller.selection = TextSelection.collapsed(offset: controller.text.length); @@ -14909,7 +14925,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -14950,9 +14966,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }); }); - testWidgets('contextMenuBuilder is used in place of the default text selection toolbar', (WidgetTester tester) async { + testWidgetsWithLeakTracking('contextMenuBuilder is used in place of the default text selection toolbar', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); - final TextEditingController controller = TextEditingController(text: ''); await tester.pumpWidget(MaterialApp( home: Align( alignment: Alignment.topLeft, @@ -14963,7 +14978,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.subtitle1!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15001,14 +15016,16 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); group('Spell check', () { - testWidgets( + testWidgetsWithLeakTracking( 'Spell check configured properly when spell check disabled by default', (WidgetTester tester) async { + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15023,14 +15040,16 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(state.spellCheckEnabled, isFalse); }); - testWidgets( + testWidgetsWithLeakTracking( 'Spell check configured properly when spell check disabled manually', (WidgetTester tester) async { + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15046,14 +15065,16 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(state.spellCheckEnabled, isFalse); }); - testWidgets( + testWidgetsWithLeakTracking( 'Error thrown when spell check configuration defined without specifying misspelled text style', (WidgetTester tester) async { + controller.text = 'A'; + expect( () { EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15066,17 +15087,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets( + testWidgetsWithLeakTracking( 'Spell check configured properly when spell check enabled without specified spell check service and native spell check service defined', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; + controller.text = 'A'; await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15100,16 +15122,17 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async tester.binding.platformDispatcher.clearNativeSpellCheckServiceDefined(); }); - testWidgets( + testWidgetsWithLeakTracking( 'Spell check configured properly with specified spell check service', (WidgetTester tester) async { final FakeSpellCheckService fakeSpellCheckService = FakeSpellCheckService(); + controller.text = 'A'; await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15132,17 +15155,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets( + testWidgetsWithLeakTracking( 'Spell check disabled when spell check configuration specified but no default spell check service available', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = false; + controller.text = 'A'; await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15163,16 +15187,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async tester.binding.platformDispatcher.clearNativeSpellCheckServiceDefined(); }); - testWidgets( + testWidgetsWithLeakTracking( 'findSuggestionSpanAtCursorIndex finds correct span with cursor in middle of a word', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15206,16 +15232,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(suggestionSpan, equals(expectedSpan)); }); - testWidgets( + testWidgetsWithLeakTracking( 'findSuggestionSpanAtCursorIndex finds correct span with cursor on edge of a word', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15248,16 +15276,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(suggestionSpan, equals(expectedSpan)); }); - testWidgets( + testWidgetsWithLeakTracking( 'findSuggestionSpanAtCursorIndex finds no span when cursor out of range of spans', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15290,16 +15320,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(suggestionSpan, isNull); }); - testWidgets( + testWidgetsWithLeakTracking( 'findSuggestionSpanAtCursorIndex finds no span when word correctly spelled', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; + controller.text = 'A'; + await tester.pumpWidget( MaterialApp( home: EditableText( - controller: TextEditingController(text: 'A'), - focusNode: FocusNode(), + controller: controller, + focusNode: focusNode, style: const TextStyle(), cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15332,7 +15364,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(suggestionSpan, isNull); }); - testWidgets('can show spell check suggestions toolbar when there are spell check results', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can show spell check suggestions toolbar when there are spell check results', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; const TextEditingValue value = TextEditingValue( @@ -15393,7 +15425,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(find.text('DELETE'), matcher); }); - testWidgets('can show spell check suggestions toolbar when there are no spell check results on iOS', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can show spell check suggestions toolbar when there are no spell check results on iOS', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; const TextEditingValue value = TextEditingValue( @@ -15455,7 +15487,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('cupertino spell check suggestions toolbar buttons correctly change the composing region', (WidgetTester tester) async { + testWidgetsWithLeakTracking('cupertino spell check suggestions toolbar buttons correctly change the composing region', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; const TextEditingValue value = TextEditingValue( @@ -15516,7 +15548,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async } }); - testWidgets('material spell check suggestions toolbar buttons correctly change the composing region', (WidgetTester tester) async { + testWidgetsWithLeakTracking('material spell check suggestions toolbar buttons correctly change the composing region', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; const TextEditingValue value = TextEditingValue( @@ -15581,7 +15613,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async } }); - testWidgets('replacing puts cursor at the end of the word', (WidgetTester tester) async { + testWidgetsWithLeakTracking('replacing puts cursor at the end of the word', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; controller.value = const TextEditingValue( @@ -15693,7 +15725,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('tapping on a misspelled word hides the handles', (WidgetTester tester) async { + testWidgetsWithLeakTracking('tapping on a misspelled word hides the handles', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; controller.value = const TextEditingValue( @@ -15756,12 +15788,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }); group('magnifier', () { - testWidgets('should build nothing by default', (WidgetTester tester) async { + testWidgetsWithLeakTracking('should build nothing by default', (WidgetTester tester) async { final EditableText editableText = EditableText( controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15777,27 +15809,29 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); final BuildContext context = tester.firstElement(find.byType(EditableText)); + final ValueNotifier notifier = ValueNotifier(MagnifierInfo.empty); + addTearDown(notifier.dispose); expect( - editableText.magnifierConfiguration.magnifierBuilder( - context, - MagnifierController(), - ValueNotifier(MagnifierInfo.empty) - ), - isNull, + editableText.magnifierConfiguration.magnifierBuilder( + context, + MagnifierController(), + notifier, + ), + isNull, ); }); }); // Regression test for: https://github.com/flutter/flutter/issues/117418. - testWidgets('can handle the partial selection of a multi-code-unit glyph', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can handle the partial selection of a multi-code-unit glyph', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( controller: controller, showSelectionHandles: true, autofocus: true, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15833,12 +15867,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(tester.takeException(), null); }); - testWidgets('does not crash when didChangeMetrics is called after unmounting', (WidgetTester tester) async { + testWidgetsWithLeakTracking('does not crash when didChangeMetrics is called after unmounting', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( controller: controller, - focusNode: FocusNode(), + focusNode: focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, @@ -15855,13 +15889,9 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async state.didChangeMetrics(); }); - testWidgets('_CompositionCallback widget does not skip frames', (WidgetTester tester) async { + testWidgetsWithLeakTracking('_CompositionCallback widget does not skip frames', (WidgetTester tester) async { EditableText.debugDeterministicCursor = true; - final FocusNode focusNode = FocusNode(); - final TextEditingController controller = TextEditingController.fromValue( - const TextEditingValue(selection: TextSelection.collapsed(offset: 0)), - ); - + controller.value = const TextEditingValue(selection: TextSelection.collapsed(offset: 0)); Offset offset = Offset.zero; late StateSetter setState; @@ -15916,13 +15946,17 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }); group('selection behavior when receiving focus', () { - testWidgets('tabbing between fields', (WidgetTester tester) async { + testWidgetsWithLeakTracking('tabbing between fields', (WidgetTester tester) async { final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); controller1.text = 'Text1'; controller2.text = 'Text2\nLine2'; final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); await tester.pumpWidget( MaterialApp( @@ -16052,11 +16086,9 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets('Selection is updated when the field has focus and the new selection is invalid', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Selection is updated when the field has focus and the new selection is invalid', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/120631. - final TextEditingController controller = TextEditingController(); controller.text = 'Text'; - final FocusNode focusNode = FocusNode(); await tester.pumpWidget( MaterialApp( @@ -16111,11 +16143,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets('when having focus stolen between frames on web', (WidgetTester tester) async { - final TextEditingController controller1 = TextEditingController(); - controller1.text = 'Text1'; + testWidgetsWithLeakTracking('when having focus stolen between frames on web', (WidgetTester tester) async { + controller.text = 'Text1'; final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); await tester.pumpWidget( MaterialApp( @@ -16123,8 +16156,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async crossAxisAlignment: CrossAxisAlignment.start, children: [ EditableText( - key: ValueKey(controller1.text), - controller: controller1, + key: ValueKey(controller.text), + controller: controller, focusNode: focusNode1, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, @@ -16144,7 +16177,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(focusNode1.hasFocus, isFalse); expect(focusNode2.hasFocus, isFalse); expect( - controller1.selection, + controller.selection, const TextSelection.collapsed(offset: -1), ); @@ -16153,7 +16186,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async // Set the text editing value in order to trigger an internal call to // requestFocus. state.userUpdateTextEditingValue( - controller1.value, + controller.value, SelectionChangedCause.keyboard, ); // Focus takes a frame to update, so it hasn't changed yet. @@ -16174,10 +16207,10 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(focusNode1.hasFocus, isTrue); expect(focusNode2.hasFocus, isFalse); expect( - controller1.selection, + controller.selection, TextSelection( baseOffset: 0, - extentOffset: controller1.text.length, + extentOffset: controller.text.length, ), ); }, @@ -16185,7 +16218,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async ); }); - testWidgets('EditableText respects MediaQuery.boldText', (WidgetTester tester) async { + testWidgetsWithLeakTracking('EditableText respects MediaQuery.boldText', (WidgetTester tester) async { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -16206,12 +16239,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(state.buildTextSpan().style!.fontWeight, FontWeight.bold); }); - testWidgets('code points are treated as single characters in obscure mode', (WidgetTester tester) async { + testWidgetsWithLeakTracking('code points are treated as single characters in obscure mode', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16342,12 +16375,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('when manually placing the cursor in the middle of a code point', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when manually placing the cursor in the middle of a code point', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16426,12 +16459,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('when inserting a malformed string', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when inserting a malformed string', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16488,12 +16521,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('when inserting a malformed string that is a sequence of dangling high surrogates', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when inserting a malformed string that is a sequence of dangling high surrogates', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16548,12 +16581,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async skip: kIsWeb, // [intended] ); - testWidgets('when inserting a malformed string that is a sequence of dangling low surrogates', (WidgetTester tester) async { + testWidgetsWithLeakTracking('when inserting a malformed string that is a sequence of dangling low surrogates', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16625,12 +16658,12 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async .defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); }); - testWidgets('web avoids the paste permissions prompt by not calling hasStrings', (WidgetTester tester) async { + testWidgetsWithLeakTracking('web avoids the paste permissions prompt by not calling hasStrings', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( backgroundCursorColor: Colors.grey, - controller: TextEditingController(), + controller: controller, focusNode: focusNode, obscureText: true, toolbarOptions: const ToolbarOptions( @@ -16658,16 +16691,18 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async }); }); - testWidgets('Cursor color with an opacity is respected', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Cursor color with an opacity is respected', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); const double opacity = 0.55; + controller.text = 'blah blah'; + await tester.pumpWidget( MaterialApp( home: EditableText( key: key, cursorColor: cursorColor.withOpacity(opacity), backgroundCursorColor: Colors.grey, - controller: TextEditingController(text: 'blah blah'), + controller: controller, focusNode: focusNode, style: textStyle, ), @@ -16964,6 +16999,16 @@ class TransformedEditableText extends StatefulWidget { class _TransformedEditableTextState extends State { bool _isTransformed = false; + final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return MediaQuery( @@ -16976,8 +17021,8 @@ class _TransformedEditableTextState extends State { Transform.translate( offset: _isTransformed ? widget.offset : Offset.zero, child: EditableText( - controller: TextEditingController(), - focusNode: FocusNode(), + controller: _controller, + focusNode: _focusNode, style: Typography.material2018().black.titleMedium!, cursorColor: Colors.blue, backgroundCursorColor: Colors.grey, diff --git a/packages/flutter/test/widgets/ensure_visible_test.dart b/packages/flutter/test/widgets/ensure_visible_test.dart index 16f493804cad..329388ee11a8 100644 --- a/packages/flutter/test/widgets/ensure_visible_test.dart +++ b/packages/flutter/test/widgets/ensure_visible_test.dart @@ -7,6 +7,7 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; Finder findKey(int i) => find.byKey(ValueKey(i), skipOffstage: false); @@ -68,7 +69,7 @@ Widget buildListView(Axis scrollDirection, { bool reverse = false, bool shrinkWr void main() { group('SingleChildScrollView', () { - testWidgets('SingleChildScrollView ensureVisible Axis.vertical', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SingleChildScrollView ensureVisible Axis.vertical', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); await tester.pumpWidget(buildSingleChildScrollView(Axis.vertical)); @@ -95,7 +96,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dy, equals(100.0)); }); - testWidgets('SingleChildScrollView ensureVisible Axis.horizontal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SingleChildScrollView ensureVisible Axis.horizontal', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); await tester.pumpWidget(buildSingleChildScrollView(Axis.horizontal)); @@ -122,7 +123,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dx, equals(100.0)); }); - testWidgets('SingleChildScrollView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SingleChildScrollView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); await tester.pumpWidget(buildSingleChildScrollView(Axis.vertical, reverse: true)); @@ -190,7 +191,7 @@ void main() { expect(tester.getBottomLeft(findKey(6)).dy, equals(500.0)); }); - testWidgets('SingleChildScrollView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SingleChildScrollView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); await tester.pumpWidget(buildSingleChildScrollView(Axis.horizontal, reverse: true)); @@ -263,7 +264,7 @@ void main() { expect(tester.getBottomLeft(findKey(6)).dx, equals(500.0)); }); - testWidgets('SingleChildScrollView ensureVisible rotated child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('SingleChildScrollView ensureVisible rotated child', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); await tester.pumpWidget( @@ -310,7 +311,7 @@ void main() { expect(tester.getTopLeft(findKey(0)).dy, moreOrLessEquals(500.0, epsilon: 0.1)); }); - testWidgets('Nested SingleChildScrollView ensureVisible behavior test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Nested SingleChildScrollView ensureVisible behavior test', (WidgetTester tester) async { // Regressing test for https://github.com/flutter/flutter/issues/65100 Finder findKey(String coordinate) => find.byKey(ValueKey(coordinate)); BuildContext findContext(String coordinate) => tester.element(findKey(coordinate)); @@ -388,7 +389,7 @@ void main() { }); group('ListView', () { - testWidgets('ListView ensureVisible Axis.vertical', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.vertical', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -424,7 +425,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dy, equals(100.0)); }); - testWidgets('ListView ensureVisible Axis.horizontal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.horizontal', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -460,7 +461,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dx, equals(100.0)); }); - testWidgets('ListView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -536,7 +537,7 @@ void main() { expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); }); - testWidgets('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -617,7 +618,7 @@ void main() { expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); }); - testWidgets('ListView ensureVisible negative child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible negative child', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -675,7 +676,7 @@ void main() { expect(getOffset(), equals(-400.0)); }); - testWidgets('ListView ensureVisible rotated child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible rotated child', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -728,7 +729,7 @@ void main() { }); group('ListView shrinkWrap', () { - testWidgets('ListView ensureVisible Axis.vertical', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.vertical', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -764,7 +765,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dy, equals(100.0)); }); - testWidgets('ListView ensureVisible Axis.horizontal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.horizontal', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -800,7 +801,7 @@ void main() { expect(tester.getTopLeft(findKey(3)).dx, equals(100.0)); }); - testWidgets('ListView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.vertical reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -876,7 +877,7 @@ void main() { expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); }); - testWidgets('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); @@ -959,7 +960,7 @@ void main() { }); group('Scrollable with center', () { - testWidgets('ensureVisible', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ensureVisible', (WidgetTester tester) async { BuildContext findContext(int i) => tester.element(findKey(i)); Future prepare(double offset) async { tester.state(find.byType(Scrollable)).position.jumpTo(offset); diff --git a/packages/flutter/test/widgets/error_widget_builder_test.dart b/packages/flutter/test/widgets/error_widget_builder_test.dart index 141f522209d7..c5c075897ade 100644 --- a/packages/flutter/test/widgets/error_widget_builder_test.dart +++ b/packages/flutter/test/widgets/error_widget_builder_test.dart @@ -4,9 +4,10 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('ErrorWidget.builder', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ErrorWidget.builder', (WidgetTester tester) async { final ErrorWidgetBuilder oldBuilder = ErrorWidget.builder; ErrorWidget.builder = (FlutterErrorDetails details) { return const Text('oopsie!', textDirection: TextDirection.ltr); @@ -25,7 +26,7 @@ void main() { ErrorWidget.builder = oldBuilder; }); - testWidgets('ErrorWidget.builder', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ErrorWidget.builder', (WidgetTester tester) async { final ErrorWidgetBuilder oldBuilder = ErrorWidget.builder; ErrorWidget.builder = (FlutterErrorDetails details) { return ErrorWidget(''); diff --git a/packages/flutter/test/widgets/fade_in_image_test.dart b/packages/flutter/test/widgets/fade_in_image_test.dart index e88c6ca6e508..e12f2ecb7678 100644 --- a/packages/flutter/test/widgets/fade_in_image_test.dart +++ b/packages/flutter/test/widgets/fade_in_image_test.dart @@ -8,6 +8,7 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../image_data.dart'; import '../painting/image_test_utils.dart'; @@ -90,14 +91,26 @@ FadeInImageParts findFadeInImage(WidgetTester tester) { } } -Future main() async { +void main() { // These must run outside test zone to complete - final ui.Image targetImage = await createTestImage(); - final ui.Image placeholderImage = await createTestImage(); - final ui.Image replacementImage = await createTestImage(); + late final ui.Image targetImage; + late final ui.Image placeholderImage; + late final ui.Image replacementImage; + + setUpAll(() async { + targetImage = await createTestImage(); + placeholderImage = await createTestImage(); + replacementImage = await createTestImage(); + }); + + tearDownAll(() { + targetImage.dispose(); + placeholderImage.dispose(); + replacementImage.dispose(); + }); group('FadeInImage', () { - testWidgets('animates an uncached image', (WidgetTester tester) async { + testWidgetsWithLeakTracking('animates an uncached image', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -147,7 +160,7 @@ Future main() async { expect(findFadeInImage(tester).target.opacity, 1); }); - testWidgets("FadeInImage's image obeys gapless playback", (WidgetTester tester) async { + testWidgetsWithLeakTracking("FadeInImage's image obeys gapless playback", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); final TestImageProvider secondImageProvider = TestImageProvider(replacementImage); @@ -188,7 +201,7 @@ Future main() async { }); // Regression test for https://github.com/flutter/flutter/issues/111011 - testWidgets("FadeInImage's image obeys gapless playback when first image is cached but second isn't", + testWidgetsWithLeakTracking("FadeInImage's image obeys gapless playback when first image is cached but second isn't", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -225,7 +238,7 @@ Future main() async { expect(parts.target.opacity, 1); }); - testWidgets("FadeInImage's placeholder obeys gapless playback", (WidgetTester tester) async { + testWidgetsWithLeakTracking("FadeInImage's placeholder obeys gapless playback", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider secondPlaceholderProvider = TestImageProvider(replacementImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -261,7 +274,7 @@ Future main() async { expect(parts.placeholder!.opacity, 1); }); - testWidgets('shows a cached image immediately when skipFadeOnSynchronousLoad=true', (WidgetTester tester) async { + testWidgetsWithLeakTracking('shows a cached image immediately when skipFadeOnSynchronousLoad=true', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); imageProvider.resolve(ImageConfiguration.empty); @@ -277,7 +290,7 @@ Future main() async { expect(findFadeInImage(tester).target.opacity, 1); }); - testWidgets('handles updating the placeholder image', (WidgetTester tester) async { + testWidgetsWithLeakTracking('handles updating the placeholder image', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider secondPlaceholderProvider = TestImageProvider(replacementImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -309,7 +322,7 @@ Future main() async { expect(findFadeInImage(tester).state, same(state)); }); - testWidgets('does not keep the placeholder in the tree if it is invisible', (WidgetTester tester) async { + testWidgetsWithLeakTracking('does not keep the placeholder in the tree if it is invisible', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -331,7 +344,7 @@ Future main() async { expect(find.byType(Image), findsOneWidget); }); - testWidgets("doesn't interrupt in-progress animation when animation values are updated", (WidgetTester tester) async { + testWidgetsWithLeakTracking("doesn't interrupt in-progress animation when animation values are updated", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -434,7 +447,7 @@ Future main() async { }); group('semantics', () { - testWidgets('only one Semantics node appears within FadeInImage', (WidgetTester tester) async { + testWidgetsWithLeakTracking('only one Semantics node appears within FadeInImage', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -446,7 +459,7 @@ Future main() async { expect(find.byType(Semantics), findsOneWidget); }); - testWidgets('is excluded if excludeFromSemantics is true', (WidgetTester tester) async { + testWidgetsWithLeakTracking('is excluded if excludeFromSemantics is true', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -462,7 +475,7 @@ Future main() async { group('label', () { const String imageSemanticText = 'Test image semantic label'; - testWidgets('defaults to image label if placeholder label is unspecified', (WidgetTester tester) async { + testWidgetsWithLeakTracking('defaults to image label if placeholder label is unspecified', (WidgetTester tester) async { Semantics semanticsWidget() => tester.widget(find.byType(Semantics)); final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); @@ -489,7 +502,7 @@ Future main() async { expect(semanticsWidget().properties.label, imageSemanticText); }); - testWidgets('is empty without any specified semantics labels', (WidgetTester tester) async { + testWidgetsWithLeakTracking('is empty without any specified semantics labels', (WidgetTester tester) async { Semantics semanticsWidget() => tester.widget(find.byType(Semantics)); final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); @@ -515,7 +528,7 @@ Future main() async { }); group("placeholder's BoxFit", () { - testWidgets("should be the image's BoxFit when not set", (WidgetTester tester) async { + testWidgetsWithLeakTracking("should be the image's BoxFit when not set", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -529,7 +542,7 @@ Future main() async { expect(findFadeInImage(tester).placeholder!.fit, equals(BoxFit.cover)); }); - testWidgets('should be the given value when set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('should be the given value when set', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -546,7 +559,7 @@ Future main() async { }); group("placeholder's FilterQuality", () { - testWidgets("should be the image's FilterQuality when not set", (WidgetTester tester) async { + testWidgetsWithLeakTracking("should be the image's FilterQuality when not set", (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); @@ -560,7 +573,7 @@ Future main() async { expect(findFadeInImage(tester).placeholder!.filterQuality, equals(FilterQuality.medium)); }); - testWidgets('should be the given value when set', (WidgetTester tester) async { + testWidgetsWithLeakTracking('should be the given value when set', (WidgetTester tester) async { final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage); final TestImageProvider imageProvider = TestImageProvider(targetImage); diff --git a/packages/flutter/test/widgets/fade_transition_test.dart b/packages/flutter/test/widgets/fade_transition_test.dart index edcfce8630e6..e35013867e8e 100644 --- a/packages/flutter/test/widgets/fade_transition_test.dart +++ b/packages/flutter/test/widgets/fade_transition_test.dart @@ -5,9 +5,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('FadeTransition', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FadeTransition', (WidgetTester tester) async { final DebugPrintCallback oldPrint = debugPrint; final List log = []; debugPrint = (String? message, { int? wrapWidth }) { diff --git a/packages/flutter/test/widgets/fitted_box_test.dart b/packages/flutter/test/widgets/fitted_box_test.dart index bc5d30cb42fc..bcc8aee897c5 100644 --- a/packages/flutter/test/widgets/fitted_box_test.dart +++ b/packages/flutter/test/widgets/fitted_box_test.dart @@ -5,9 +5,10 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('Can size according to aspect ratio', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can size according to aspect ratio', (WidgetTester tester) async { final Key outside = UniqueKey(); final Key inside = UniqueKey(); @@ -42,7 +43,7 @@ void main() { expect(insidePoint, equals(outsidePoint)); }); - testWidgets('Can contain child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can contain child', (WidgetTester tester) async { final Key outside = UniqueKey(); final Key inside = UniqueKey(); @@ -77,7 +78,7 @@ void main() { expect(insidePoint, equals(outsidePoint)); }); - testWidgets('Child can cover', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Child can cover', (WidgetTester tester) async { final Key outside = UniqueKey(); final Key inside = UniqueKey(); @@ -113,7 +114,7 @@ void main() { expect(insidePoint, equals(outsidePoint)); }); - testWidgets('FittedBox with no child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox with no child', (WidgetTester tester) async { final Key key = UniqueKey(); await tester.pumpWidget( Center( @@ -129,7 +130,7 @@ void main() { expect(box.size.height, 0.0); }); - testWidgets('Child can be aligned multiple ways in a row', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Child can be aligned multiple ways in a row', (WidgetTester tester) async { final Key outside = UniqueKey(); final Key inside = UniqueKey(); @@ -339,7 +340,7 @@ void main() { } }); - testWidgets('FittedBox layers - contain', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox layers - contain', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: SizedBox( @@ -360,7 +361,7 @@ void main() { expect(getLayers(), [TransformLayer, TransformLayer, OffsetLayer]); }); - testWidgets('FittedBox layers - cover - horizontal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox layers - cover - horizontal', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: SizedBox( @@ -383,7 +384,7 @@ void main() { expect(getLayers(), [TransformLayer, ClipRectLayer, TransformLayer, OffsetLayer]); }); - testWidgets('FittedBox layers - cover - vertical', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox layers - cover - vertical', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: SizedBox( @@ -406,7 +407,7 @@ void main() { expect(getLayers(), [TransformLayer, ClipRectLayer, TransformLayer, OffsetLayer]); }); - testWidgets('FittedBox layers - none - clip', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox layers - none - clip', (WidgetTester tester) async { final List values = [10.0, 50.0, 100.0]; for (final double a in values) { for (final double b in values) { @@ -442,7 +443,7 @@ void main() { } }); - testWidgets('Big child into small fitted box - hit testing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Big child into small fitted box - hit testing', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); bool pointerDown = false; await tester.pumpWidget( @@ -474,7 +475,7 @@ void main() { expect(pointerDown, isTrue); }); - testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can set and update clipBehavior', (WidgetTester tester) async { await tester.pumpWidget(FittedBox(fit: BoxFit.none, child: Container())); final RenderFittedBox renderObject = tester.allRenderObjects.whereType().first; expect(renderObject.clipBehavior, equals(Clip.none)); @@ -483,7 +484,7 @@ void main() { expect(renderObject.clipBehavior, equals(Clip.antiAlias)); }); - testWidgets('BoxFit.scaleDown matches size of child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('BoxFit.scaleDown matches size of child', (WidgetTester tester) async { final Key outside = UniqueKey(); final Key inside = UniqueKey(); @@ -544,7 +545,7 @@ void main() { expect(insidePoint - outsidePoint, equals(Offset.zero)); }); - testWidgets('Switching to and from BoxFit.scaleDown causes relayout', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Switching to and from BoxFit.scaleDown causes relayout', (WidgetTester tester) async { final Key outside = UniqueKey(); final Widget scaleDownWidget = Center( @@ -588,7 +589,7 @@ void main() { expect(outsideBox.size.height, 50.0); }); - testWidgets('FittedBox without child does not throw', (WidgetTester tester) async { + testWidgetsWithLeakTracking('FittedBox without child does not throw', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: SizedBox( diff --git a/packages/flutter/test/widgets/flex_test.dart b/packages/flutter/test/widgets/flex_test.dart index 1a29b997e29a..ea48423f574f 100644 --- a/packages/flutter/test/widgets/flex_test.dart +++ b/packages/flutter/test/widgets/flex_test.dart @@ -5,9 +5,10 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('Can hit test flex children of stacks', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can hit test flex children of stacks', (WidgetTester tester) async { bool didReceiveTap = false; await tester.pumpWidget( Directionality( @@ -47,7 +48,7 @@ void main() { expect(didReceiveTap, isTrue); }); - testWidgets('Flexible defaults to loose', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flexible defaults to loose', (WidgetTester tester) async { await tester.pumpWidget( const Row( textDirection: TextDirection.ltr, @@ -61,7 +62,7 @@ void main() { expect(box.size.width, 100.0); }); - testWidgets("Doesn't overflow because of floating point accumulated error", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Doesn't overflow because of floating point accumulated error", (WidgetTester tester) async { // both of these cases have failed in the past due to floating point issues await tester.pumpWidget( const Center( @@ -99,7 +100,7 @@ void main() { ); }); - testWidgets('Error information is printed correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Error information is printed correctly', (WidgetTester tester) async { // We run this twice, the first time without an error, so that the second time // we only get a single exception. Otherwise we'd get two, the one we want and // an extra one when we discover we never computed a size. @@ -133,7 +134,7 @@ void main() { expect(message, contains('\nSee also:')); }); - testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can set and update clipBehavior', (WidgetTester tester) async { await tester.pumpWidget(const Flex(direction: Axis.vertical)); final RenderFlex renderObject = tester.allRenderObjects.whereType().first; expect(renderObject.clipBehavior, equals(Clip.none)); diff --git a/packages/flutter/test/widgets/flow_test.dart b/packages/flutter/test/widgets/flow_test.dart index 0746896d9f31..2a517963e20a 100644 --- a/packages/flutter/test/widgets/flow_test.dart +++ b/packages/flutter/test/widgets/flow_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; class TestFlowDelegate extends FlowDelegate { TestFlowDelegate({required this.startOffset}) : super(repaint: startOffset); @@ -61,7 +62,7 @@ class DuplicatePainterOpacityFlowDelegate extends OpacityFlowDelegate { } void main() { - testWidgets('Flow control test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flow control test', (WidgetTester tester) async { final AnimationController startOffset = AnimationController.unbounded( vsync: tester, ); @@ -115,7 +116,7 @@ void main() { expect(log, equals([0])); }); - testWidgets('paintChild gets called twice', (WidgetTester tester) async { + testWidgetsWithLeakTracking('paintChild gets called twice', (WidgetTester tester) async { await tester.pumpWidget( Flow( delegate: DuplicatePainterOpacityFlowDelegate(1.0), @@ -137,7 +138,7 @@ void main() { )); }); - testWidgets('Flow opacity layer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flow opacity layer', (WidgetTester tester) async { const double opacity = 0.2; await tester.pumpWidget( Flow( @@ -157,7 +158,7 @@ void main() { expect(layer!.firstChild, isA()); }); - testWidgets('Flow can set and update clipBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flow can set and update clipBehavior', (WidgetTester tester) async { const double opacity = 0.2; await tester.pumpWidget( Flow( @@ -186,7 +187,7 @@ void main() { } }); - testWidgets('Flow.unwrapped can set and update clipBehavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flow.unwrapped can set and update clipBehavior', (WidgetTester tester) async { const double opacity = 0.2; await tester.pumpWidget( Flow.unwrapped(