diff --git a/packages/flutter/test/widgets/animated_size_test.dart b/packages/flutter/test/widgets/animated_size_test.dart index 75a9272d05f8f..c48729e48e6fe 100644 --- a/packages/flutter/test/widgets/animated_size_test.dart +++ b/packages/flutter/test/widgets/animated_size_test.dart @@ -435,7 +435,7 @@ void main() { ); }); - testWidgets('disposes animation and controller', (WidgetTester tester) async { + testWidgetsWithLeakTracking('disposes animation and controller', (WidgetTester tester) async { await tester.pumpWidget( const Center( child: AnimatedSize( diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index d66f3466911b6..d80e1e56670b7 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1062,13 +1062,14 @@ void main() { element.createChild(0, after: null); }); - testWidgets('GlobalKey - re-attach child to new parents, and the old parent is deactivated(unmounted)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('GlobalKey - re-attach child to new parents, and the old parent is deactivated(unmounted)', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/62055 const Key key1 = GlobalObjectKey('key1'); const Key key2 = GlobalObjectKey('key2'); late StateSetter setState; int tabBarViewCnt = 2; TabController tabController = TabController(length: tabBarViewCnt, vsync: const TestVSync()); + addTearDown(tabController.dispose); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, @@ -1101,6 +1102,7 @@ void main() { setState(() { tabBarViewCnt = 1; tabController = TabController(length: tabBarViewCnt, vsync: const TestVSync()); + addTearDown(tabController.dispose); }); await tester.pump(const Duration(seconds: 1)); // finish the animation diff --git a/packages/flutter/test/widgets/html_element_view_test.dart b/packages/flutter/test/widgets/html_element_view_test.dart index dd8866e9faafe..58961ab763368 100644 --- a/packages/flutter/test/widgets/html_element_view_test.dart +++ b/packages/flutter/test/widgets/html_element_view_test.dart @@ -15,6 +15,7 @@ import 'package:flutter/src/widgets/_html_element_view_web.dart' show debugOverridePlatformViewRegistry; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'package:web/web.dart' as web; final Object _mockHtmlElement = Object(); @@ -42,7 +43,7 @@ void main() { }); group('HtmlElementView', () { - testWidgets('Create HTML view', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Create HTML view', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); @@ -64,7 +65,7 @@ void main() { ); }); - testWidgets('Create HTML view with PlatformViewCreatedCallback', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Create HTML view with PlatformViewCreatedCallback', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); @@ -97,7 +98,7 @@ void main() { ); }); - testWidgets('Create HTML view with creation params', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Create HTML view with creation params', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); await tester.pumpWidget( @@ -132,7 +133,7 @@ void main() { ); }); - testWidgets('Resize HTML view', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Resize HTML view', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); await tester.pumpWidget( @@ -168,7 +169,7 @@ void main() { ); }); - testWidgets('Change HTML view type', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Change HTML view type', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); fakePlatformViewRegistry.registerViewFactory('maps', _mockViewFactory); @@ -200,7 +201,7 @@ void main() { ); }); - testWidgets('Dispose HTML view', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Dispose HTML view', (WidgetTester tester) async { fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); await tester.pumpWidget( const Center( @@ -227,7 +228,7 @@ void main() { ); }); - testWidgets('HTML view survives widget tree change', (WidgetTester tester) async { + testWidgetsWithLeakTracking('HTML view survives widget tree change', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory); final GlobalKey key = GlobalKey(); @@ -259,7 +260,7 @@ void main() { ); }); - testWidgets('HtmlElementView has correct semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('HtmlElementView has correct semantics', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); expect(currentViewId, greaterThanOrEqualTo(0)); @@ -306,7 +307,7 @@ void main() { debugOverridePlatformViewRegistry = null; }); - testWidgets('Create platform view from tagName', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Create platform view from tagName', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); await tester.pumpWidget( @@ -331,7 +332,7 @@ void main() { expect(htmlElement.tagName, equalsIgnoringCase('div')); }); - testWidgets('Create invisible platform view', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Create invisible platform view', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); await tester.pumpWidget( @@ -357,7 +358,7 @@ void main() { expect(htmlElement.tagName, equalsIgnoringCase('script')); }); - testWidgets('onElementCreated', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onElementCreated', (WidgetTester tester) async { final List createdElements = []; void onElementCreated(Object element) { createdElements.add(element); diff --git a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart index aa22d99cc6688..729f54e9c66af 100644 --- a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart +++ b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart @@ -55,7 +55,7 @@ void main() { }); group('construction check', () { - testWidgets('ListWheelScrollView needs positive diameter ratio', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView needs positive diameter ratio', (WidgetTester tester) async { expect( () => ListWheelScrollView( diameterRatio: nonconst(-2.0), @@ -70,7 +70,7 @@ void main() { ); }); - testWidgets('ListWheelScrollView can have zero child', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView can have zero child', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -114,7 +114,7 @@ void main() { expect(detach, 1); }); - testWidgets('ListWheelScrollView needs positive magnification', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView needs positive magnification', (WidgetTester tester) async { expect( () { ListWheelScrollView( @@ -128,7 +128,7 @@ void main() { ); }); - testWidgets('ListWheelScrollView needs valid overAndUnderCenterOpacity', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView needs valid overAndUnderCenterOpacity', (WidgetTester tester) async { expect( () { ListWheelScrollView( @@ -175,9 +175,9 @@ void main() { }); group('infinite scrolling', () { - testWidgets('infinite looping list', (WidgetTester tester) async { - final FixedExtentScrollController controller = - FixedExtentScrollController(); + testWidgetsWithLeakTracking('infinite looping list', (WidgetTester tester) async { + final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -221,9 +221,9 @@ void main() { ); }); - testWidgets('infinite child builder', (WidgetTester tester) async { - final FixedExtentScrollController controller = - FixedExtentScrollController(); + testWidgetsWithLeakTracking('infinite child builder', (WidgetTester tester) async { + final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -262,12 +262,12 @@ void main() { ); }); - testWidgets('child builder with lower and upper limits', (WidgetTester tester) async { + testWidgetsWithLeakTracking('child builder with lower and upper limits', (WidgetTester tester) async { // Adjust the content dimensions at the end of `RenderListWheelViewport.performLayout()` final List paintedChildren = []; - final FixedExtentScrollController controller = - FixedExtentScrollController(initialItem: -10); + final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: -10); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -320,9 +320,11 @@ void main() { }); group('layout', () { - testWidgets('Flings with high velocity should not break the children lower and upper limits', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Flings with high velocity should not break the children lower and upper limits', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/112526 final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); + Widget buildFrame() { return Directionality( textDirection: TextDirection.ltr, @@ -359,8 +361,10 @@ void main() { }, variant: TargetPlatformVariant(TargetPlatform.values.toSet())); // Regression test for https://github.com/flutter/flutter/issues/90953 - testWidgets('ListWheelScrollView childDelegate update test 2', (WidgetTester tester) async { - final FixedExtentScrollController controller = FixedExtentScrollController( initialItem: 2 ); + testWidgetsWithLeakTracking('ListWheelScrollView childDelegate update test 2', (WidgetTester tester) async { + final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 2); + addTearDown(controller.dispose); + Widget buildFrame(int childCount) { return Directionality( textDirection: TextDirection.ltr, @@ -422,8 +426,10 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/58144 - testWidgets('ListWheelScrollView childDelegate update test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView childDelegate update test', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); + Widget buildFrame(int childCount) { return Directionality( textDirection: TextDirection.ltr, @@ -453,7 +459,7 @@ void main() { expect(tester.renderObject(find.text('1')).attached, true); }); - testWidgets("ListWheelScrollView takes parent's size with small children", (WidgetTester tester) async { + testWidgetsWithLeakTracking("ListWheelScrollView takes parent's size with small children", (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -474,7 +480,7 @@ void main() { expect(tester.getBottomRight(find.byType(ListWheelScrollView)), const Offset(800.0, 600.0)); }); - testWidgets("ListWheelScrollView takes parent's size with large children", (WidgetTester tester) async { + testWidgetsWithLeakTracking("ListWheelScrollView takes parent's size with large children", (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -495,7 +501,7 @@ void main() { expect(tester.getBottomRight(find.byType(ListWheelScrollView)), const Offset(800.0, 600.0)); }); - testWidgets("ListWheelScrollView children can't be bigger than itemExtent", (WidgetTester tester) async { + testWidgetsWithLeakTracking("ListWheelScrollView children can't be bigger than itemExtent", (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -517,10 +523,10 @@ void main() { expect(find.text('blah'), findsOneWidget); }); - testWidgets('builder is never called twice for same index', (WidgetTester tester) async { + testWidgetsWithLeakTracking('builder is never called twice for same index', (WidgetTester tester) async { final Set builtChildren = {}; - final FixedExtentScrollController controller = - FixedExtentScrollController(); + final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -554,9 +560,9 @@ void main() { await tester.pump(); }); - testWidgets('only visible children are maintained as children of the rendered viewport', (WidgetTester tester) async { - final FixedExtentScrollController controller = - FixedExtentScrollController(); + testWidgetsWithLeakTracking('only visible children are maintained as children of the rendered viewport', (WidgetTester tester) async { + final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -591,9 +597,9 @@ void main() { expect(viewport.childCount, 4); }); - testWidgets('a tighter squeeze lays out more children', (WidgetTester tester) async { - final FixedExtentScrollController controller = - FixedExtentScrollController(initialItem: 10); + testWidgetsWithLeakTracking('a tighter squeeze lays out more children', (WidgetTester tester) async { + final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -636,7 +642,7 @@ void main() { expect(viewport.childCount, 13); }); - testWidgets('Active children are laid out with correct offset', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Active children are laid out with correct offset', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/123497 Future buildWidget(double width) async { return tester.pumpWidget( @@ -673,8 +679,9 @@ void main() { }); group('pre-transform viewport', () { - testWidgets('ListWheelScrollView starts and ends from the middle', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView starts and ends from the middle', (WidgetTester tester) async { final ScrollController controller = ScrollController(); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -715,8 +722,9 @@ void main() { expect(paintedChildren, [96, 97, 98, 99]); }); - testWidgets('A child gets painted as soon as its first pixel is in the viewport', (WidgetTester tester) async { + testWidgetsWithLeakTracking('A child gets painted as soon as its first pixel is in the viewport', (WidgetTester tester) async { final ScrollController controller = ScrollController(initialScrollOffset: 50.0); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -749,8 +757,9 @@ void main() { expect(paintedChildren, [0, 1, 2, 3, 4]); }); - testWidgets('A child is no longer painted after its last pixel leaves the viewport', (WidgetTester tester) async { + testWidgetsWithLeakTracking('A child is no longer painted after its last pixel leaves the viewport', (WidgetTester tester) async { final ScrollController controller = ScrollController(initialScrollOffset: 250.0); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -793,7 +802,7 @@ void main() { }); group('viewport transformation', () { - testWidgets('Center child is magnified', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Center child is magnified', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -817,7 +826,7 @@ void main() { ); }); - testWidgets('Default middle transform', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Default middle transform', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -846,8 +855,10 @@ void main() { )); }); - testWidgets('Curve the wheel to the left', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Curve the wheel to the left', (WidgetTester tester) async { final ScrollController controller = ScrollController(initialScrollOffset: 300.0); + addTearDown(controller.dispose); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -871,8 +882,9 @@ void main() { ); }); - testWidgets('Scrolling, diameterRatio, perspective all changes matrix', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Scrolling, diameterRatio, perspective all changes matrix', (WidgetTester tester) async { final ScrollController controller = ScrollController(initialScrollOffset: 200.0); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -990,8 +1002,9 @@ void main() { )); }); - testWidgets('offAxisFraction, magnification changes matrix', (WidgetTester tester) async { + testWidgetsWithLeakTracking('offAxisFraction, magnification changes matrix', (WidgetTester tester) async { final ScrollController controller = ScrollController(initialScrollOffset: 200.0); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -1089,7 +1102,7 @@ void main() { }); group('scroll notifications', () { - testWidgets('no onSelectedItemChanged callback on first build', (WidgetTester tester) async { + testWidgetsWithLeakTracking('no onSelectedItemChanged callback on first build', (WidgetTester tester) async { bool itemChangeCalled = false; void onItemChange(int _) { itemChangeCalled = true; } @@ -1114,7 +1127,7 @@ void main() { expect(itemChangeCalled, false); }); - testWidgets('onSelectedItemChanged when a new item is closest to center', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onSelectedItemChanged when a new item is closest to center', (WidgetTester tester) async { final List selectedItems = []; await tester.pumpWidget( @@ -1151,7 +1164,7 @@ void main() { expect(selectedItems, [1, 2, 1]); }); - testWidgets('onSelectedItemChanged reports only in valid range', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onSelectedItemChanged reports only in valid range', (WidgetTester tester) async { final List selectedItems = []; await tester.pumpWidget( @@ -1187,8 +1200,9 @@ void main() { }); group('scroll controller', () { - testWidgets('initialItem', (WidgetTester tester) async { + testWidgetsWithLeakTracking('initialItem', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -1213,8 +1227,9 @@ void main() { expect(controller.selectedItem, 10); }); - testWidgets('controller jump', (WidgetTester tester) async { + testWidgetsWithLeakTracking('controller jump', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -1245,8 +1260,9 @@ void main() { expect(controller.selectedItem, 0); }); - testWidgets('controller animateToItem', (WidgetTester tester) async { + testWidgetsWithLeakTracking('controller animateToItem', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); final List paintedChildren = []; await tester.pumpWidget( @@ -1282,9 +1298,10 @@ void main() { expect(controller.selectedItem, 0); }); - testWidgets('onSelectedItemChanged and controller are in sync', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onSelectedItemChanged and controller are in sync', (WidgetTester tester) async { final List selectedItems = []; final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -1334,8 +1351,7 @@ void main() { await tester.drag(find.byType(ListWheelScrollView), const Offset(0.0, -500.0)); await tester.pump(); - final FixedExtentScrollController controller1 = - FixedExtentScrollController(initialItem: 30); + final FixedExtentScrollController controller1 = FixedExtentScrollController(initialItem: 30); addTearDown(controller1.dispose); // Attaching first controller. @@ -1360,8 +1376,7 @@ void main() { expect(controller1.selectedItem, 50); expect(controller1.position.pixels, 5000.0); - final FixedExtentScrollController controller2 = - FixedExtentScrollController(initialItem: 33); + final FixedExtentScrollController controller2 = FixedExtentScrollController(initialItem: 33); addTearDown(controller2.dispose); // Attaching the second controller. @@ -1407,8 +1422,7 @@ void main() { }); testWidgetsWithLeakTracking('controller can be reused', (WidgetTester tester) async { - final FixedExtentScrollController controller = - FixedExtentScrollController(initialItem: 3); + final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 3); addTearDown(controller.dispose); await tester.pumpWidget( @@ -1458,8 +1472,9 @@ void main() { }); group('physics', () { - testWidgets('fling velocities too low snaps back to the same item', (WidgetTester tester) async { + testWidgetsWithLeakTracking('fling velocities too low snaps back to the same item', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 40); + addTearDown(controller.dispose); final List scrolledPositions = []; await tester.pumpWidget( @@ -1508,8 +1523,9 @@ void main() { expect(scrolledPositions.last, moreOrLessEquals(40 * 1000.0, epsilon: 0.2)); }); - testWidgets('high fling velocities lands exactly on items', (WidgetTester tester) async { + testWidgetsWithLeakTracking('high fling velocities lands exactly on items', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 40); + addTearDown(controller.dispose); final List scrolledPositions = []; await tester.pumpWidget( @@ -1561,9 +1577,11 @@ void main() { }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); }); - testWidgets('ListWheelScrollView getOffsetToReveal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView getOffsetToReveal', (WidgetTester tester) async { List outerChildren; final List innerChildren = List.generate(10, (int index) => Container()); + final ScrollController controller = ScrollController(initialScrollOffset: 300.0); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -1573,7 +1591,7 @@ void main() { height: 500.0, width: 300.0, child: ListWheelScrollView( - controller: ScrollController(initialScrollOffset: 300.0), + controller: controller, itemExtent: 100.0, children: outerChildren = List.generate(10, (int i) { return Center( @@ -1629,7 +1647,10 @@ void main() { expect(revealed.rect, const Rect.fromLTWH(165.0, 265.0, 10.0, 10.0)); }); - testWidgets('will not assert on getOffsetToReveal Axis', (WidgetTester tester) async { + testWidgetsWithLeakTracking('will not assert on getOffsetToReveal Axis', (WidgetTester tester) async { + final ScrollController controller = ScrollController(initialScrollOffset: 300.0); + addTearDown(controller.dispose); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -1638,7 +1659,7 @@ void main() { height: 500.0, width: 300.0, child: ListWheelScrollView( - controller: ScrollController(initialScrollOffset: 300.0), + controller: controller, itemExtent: 100.0, children: List.generate(10, (int i) { return Center( @@ -1660,10 +1681,11 @@ void main() { viewport.getOffsetToReveal(target, 0.0, axis: Axis.horizontal); }); - testWidgets('ListWheelScrollView showOnScreen', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView showOnScreen', (WidgetTester tester) async { List outerChildren; final List innerChildren = List.generate(10, (int index) => Container()); - ScrollController controller; + final ScrollController controller = ScrollController(initialScrollOffset: 300.0); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( @@ -1673,7 +1695,7 @@ void main() { height: 500.0, width: 300.0, child: ListWheelScrollView( - controller: controller = ScrollController(initialScrollOffset: 300.0), + controller: controller, itemExtent: 100.0, children: outerChildren = List.generate(10, (int i) { @@ -1716,8 +1738,9 @@ void main() { }); group('gestures', () { - testWidgets('ListWheelScrollView allows taps for on its children', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView allows taps for on its children', (WidgetTester tester) async { final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10); + addTearDown(controller.dispose); final List children = List.generate(100, (int index) => index); final List paintedChildren = []; final Set tappedChildren = {}; @@ -1758,8 +1781,9 @@ void main() { expect(tappedChildren, paintedChildren); }); - testWidgets('ListWheelScrollView allows for horizontal drags on its children', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView allows for horizontal drags on its children', (WidgetTester tester) async { final PageController pageController = PageController(); + addTearDown(pageController.dispose); await tester.pumpWidget( Directionality( @@ -1785,10 +1809,11 @@ void main() { expect(pageController.page, 1.0); }); - testWidgets('ListWheelScrollView does not crash and does not allow taps on children that were laid out, but not painted', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView does not crash and does not allow taps on children that were laid out, but not painted', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/126491 final FixedExtentScrollController controller = FixedExtentScrollController(); + addTearDown(controller.dispose); final List children = List.generate(100, (int index) => index); final List paintedChildren = []; final Set tappedChildren = {}; @@ -1845,7 +1870,7 @@ void main() { }); }); - testWidgets('ListWheelScrollView creates only one opacity layer for all children', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ListWheelScrollView creates only one opacity layer for all children', (WidgetTester tester) async { await tester.pumpWidget( ListWheelScrollView( overAndUnderCenterOpacity: 0.5, diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 8044583a7b276..9b28b0923afa9 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -121,7 +121,7 @@ class SlideInOutPageRoute extends PageRouteBuilder { } void main() { - testWidgets('Can navigator navigate to and from a stateful widget', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can navigator navigate to and from a stateful widget', (WidgetTester tester) async { final Map routes = { '/': (BuildContext context) => const FirstWidget(), // X '/second': (BuildContext context) => const SecondWidget(), // Y @@ -171,7 +171,7 @@ void main() { expect(find.text('Y', skipOffstage: false), findsNothing); }); - testWidgets('Navigator.of fails gracefully when not found in context', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.of fails gracefully when not found in context', (WidgetTester tester) async { const Key targetKey = Key('foo'); dynamic exception; final Widget widget = ThirdWidget( @@ -186,7 +186,7 @@ void main() { expect('$exception', startsWith('Navigator operation requested with a context')); }); - testWidgets('Navigator can push Route created through page class as Pageless route', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator can push Route created through page class as Pageless route', (WidgetTester tester) async { final GlobalKey nav = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -206,7 +206,7 @@ void main() { expect(find.text('home'), findsOneWidget); }); - testWidgets('Navigator can set clip behavior', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator can set clip behavior', (WidgetTester tester) async { const MaterialPage page = MaterialPage(child: Text('page')); await tester.pumpWidget( MediaQuery( @@ -239,7 +239,7 @@ void main() { expect(tester.widget(find.byType(Overlay)).clipBehavior, Clip.none); }); - testWidgets('Zero transition page-based route correctly notifies observers when it is popped', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Zero transition page-based route correctly notifies observers when it is popped', (WidgetTester tester) async { final List> pages = >[ const ZeroTransitionPage(name: 'Page 1'), const ZeroTransitionPage(name: 'Page 2'), @@ -279,7 +279,7 @@ void main() { expect(observations[0].previous, 'Page 1'); }); - testWidgets('Navigator.of rootNavigator finds root Navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.of rootNavigator finds root Navigator', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Material( child: Column( @@ -347,7 +347,7 @@ void main() { expect(tester.getTopLeft(find.text('Dialog')).dy, 0.0); }); - testWidgets('Gestures between push and build are ignored', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Gestures between push and build are ignored', (WidgetTester tester) async { final List log = []; final Map routes = { '/': (BuildContext context) { @@ -377,7 +377,7 @@ void main() { expect(log, equals(['left'])); }); - testWidgets('pushnamed can handle Object as type', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushnamed can handle Object as type', (WidgetTester tester) async { final GlobalKey nav = GlobalKey(); final Map routes = { '/': (BuildContext context) => const Text('/'), @@ -397,7 +397,7 @@ void main() { expect(find.text('/second'), findsOneWidget); }); - testWidgets('Pending gestures are rejected', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Pending gestures are rejected', (WidgetTester tester) async { final List log = []; final Map routes = { '/': (BuildContext context) { @@ -428,7 +428,7 @@ void main() { expect(log, equals(['left'])); }); - testWidgets('popAndPushNamed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('popAndPushNamed', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.popAndPushNamed(context, '/B'); }), @@ -455,7 +455,7 @@ void main() { expect(find.text('B'), findsOneWidget); }); - testWidgets('popAndPushNamed with explicit void type parameter', (WidgetTester tester) async { + testWidgetsWithLeakTracking('popAndPushNamed with explicit void type parameter', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.popAndPushNamed(context, '/B'); }), @@ -482,7 +482,7 @@ void main() { expect(find.text('B'), findsOneWidget); }); - testWidgets('Push and pop should trigger the observers', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Push and pop should trigger the observers', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }), @@ -542,7 +542,7 @@ void main() { expect(isPopped, isTrue); }); - testWidgets('Add and remove an observer should work', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Add and remove an observer should work', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }), @@ -589,7 +589,7 @@ void main() { expect(isPopped, isFalse); }); - testWidgets('initial route trigger observer in the right order', (WidgetTester tester) async { + testWidgetsWithLeakTracking('initial route trigger observer in the right order', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => const Text('/'), '/A': (BuildContext context) => const Text('A'), @@ -663,7 +663,7 @@ void main() { MemoryAllocations.instance.removeListener(listener); }); - testWidgets('Route didAdd and dispose in same frame work', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Route didAdd and dispose in same frame work', (WidgetTester tester) async { // Regression Test for https://github.com/flutter/flutter/issues/61346. Widget buildNavigator() { return Navigator( @@ -676,6 +676,8 @@ void main() { ); } final TabController controller = TabController(length: 3, vsync: tester); + addTearDown(controller.dispose); + await tester.pumpWidget( TestDependencies( child: TabBarView( @@ -694,7 +696,7 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Page-based route pop before push finishes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Page-based route pop before push finishes', (WidgetTester tester) async { List> pages = >[const MaterialPage(child: Text('Page 1'))]; final GlobalKey navigator = GlobalKey(); Widget buildNavigator() { @@ -730,7 +732,7 @@ void main() { expect(find.text('Page 1'), findsOneWidget); }); - testWidgets('Pages update does update overlay correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Pages update does update overlay correctly', (WidgetTester tester) async { // Regression Test for https://github.com/flutter/flutter/issues/64941. List> pages = const >[ MaterialPage( @@ -779,7 +781,7 @@ void main() { expect(find.text('page 0'), findsNothing); }); - testWidgets('replaceNamed replaces', (WidgetTester tester) async { + testWidgetsWithLeakTracking('replaceNamed replaces', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pushReplacementNamed(context, '/B'); }), @@ -801,7 +803,7 @@ void main() { expect(find.text('B'), findsOneWidget); }); - testWidgets('pushReplacement sets secondaryAnimation after transition, with history change during transition', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushReplacement sets secondaryAnimation after transition, with history change during transition', (WidgetTester tester) async { final Map> routes = >{}; final Map builders = { '/' : (BuildContext context) => OnTapPage( @@ -858,7 +860,7 @@ void main() { expect(routes['/A']!.secondaryAnimation!.value, equals(routes['/C']!.animation!.value)); }); - testWidgets('new route removed from navigator history during pushReplacement transition', (WidgetTester tester) async { + testWidgetsWithLeakTracking('new route removed from navigator history during pushReplacement transition', (WidgetTester tester) async { final Map> routes = >{}; final Map builders = { '/' : (BuildContext context) => OnTapPage( @@ -904,7 +906,7 @@ void main() { expect(routes['/']!.animation!.value, equals(1.0)); }); - testWidgets('pushReplacement triggers secondaryAnimation', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushReplacement triggers secondaryAnimation', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage( id: '/', @@ -953,7 +955,7 @@ void main() { expect(aOffset.dx, lessThan(aOffsetOriginal.dx)); }); - testWidgets('pushReplacement correctly reports didReplace to the observer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushReplacement correctly reports didReplace to the observer', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/56892. final Map routes = { '/' : (BuildContext context) => const OnTapPage( @@ -1021,7 +1023,7 @@ void main() { expect(find.text('C'), isOnstage); }); - testWidgets('Able to pop all routes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Able to pop all routes', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => const OnTapPage( id: '/', @@ -1048,7 +1050,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('pushAndRemoveUntil triggers secondaryAnimation', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushAndRemoveUntil triggers secondaryAnimation', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage( id: '/', @@ -1102,7 +1104,7 @@ void main() { expect(find.text('B'), isOnstage); }); - testWidgets('pushAndRemoveUntil does not remove routes below the first route that pass the predicate', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushAndRemoveUntil does not remove routes below the first route that pass the predicate', (WidgetTester tester) async { // Regression https://github.com/flutter/flutter/issues/56688 final GlobalKey navigator = GlobalKey(); final Map routes = { @@ -1138,7 +1140,7 @@ void main() { expect(find.text('home'), isOnstage); }); - testWidgets('replaceNamed returned value', (WidgetTester tester) async { + testWidgetsWithLeakTracking('replaceNamed returned value', (WidgetTester tester) async { late Future value; final Map routes = { @@ -1187,7 +1189,7 @@ void main() { expect(replaceNamedValue, 'B'); }); - testWidgets('removeRoute', (WidgetTester tester) async { + testWidgetsWithLeakTracking('removeRoute', (WidgetTester tester) async { final Map pageBuilders = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pushNamed(context, '/B'); }), @@ -1273,7 +1275,7 @@ void main() { expect(previousRoute, routes['/']); }); - testWidgets('remove a route whose value is awaited', (WidgetTester tester) async { + testWidgetsWithLeakTracking('remove a route whose value is awaited', (WidgetTester tester) async { late Future pageValue; final Map pageBuilders = { '/': (BuildContext context) => OnTapPage(id: '/', onTap: () { pageValue = Navigator.pushNamed(context, '/A'); }), @@ -1301,7 +1303,7 @@ void main() { navigator.removeRoute(routes['/A']!); // stack becomes /, pageValue will not complete }); - testWidgets('replacing route can be observed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('replacing route can be observed', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final List log = []; final TestObserver observer = TestObserver() @@ -1371,7 +1373,7 @@ void main() { expect(log, ['pushed / (previous is )', 'pushed B (previous is /)', 'pushed C (previous is B)', 'replaced B with D']); }); - testWidgets('didStartUserGesture observable', (WidgetTester tester) async { + testWidgetsWithLeakTracking('didStartUserGesture observable', (WidgetTester tester) async { final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }), '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }), @@ -1402,7 +1404,7 @@ void main() { expect(observedPreviousRoute.settings.name, '/'); }); - testWidgets('ModalRoute.of sets up a route to rebuild if its state changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ModalRoute.of sets up a route to rebuild if its state changes', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final List log = []; late Route routeB; @@ -1469,7 +1471,7 @@ void main() { expect(log, ['building B', 'building C', 'found C', 'building D']); }); - testWidgets("Routes don't rebuild just because their animations ended", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Routes don't rebuild just because their animations ended", (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final List log = []; Route? nextRoute = PageRouteBuilder( @@ -1512,7 +1514,7 @@ void main() { expect(log, ['building page 1 - false', 'building page 2 - false', 'building page 3 - false']); }); - testWidgets('route semantics', (WidgetTester tester) async { + testWidgetsWithLeakTracking('route semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final Map routes = { '/': (BuildContext context) => OnTapPage(id: '1', onTap: () { Navigator.pushNamed(context, '/A'); }), @@ -1569,7 +1571,7 @@ void main() { semantics.dispose(); }); - testWidgets('arguments for named routes on Navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('arguments for named routes on Navigator', (WidgetTester tester) async { late GlobalKey currentRouteKey; final List arguments = []; @@ -1643,7 +1645,7 @@ void main() { arguments.clear(); }); - testWidgets('arguments for named routes on NavigatorState', (WidgetTester tester) async { + testWidgetsWithLeakTracking('arguments for named routes on NavigatorState', (WidgetTester tester) async { final GlobalKey navigatorKey = GlobalKey(); final List arguments = []; @@ -1714,7 +1716,7 @@ void main() { arguments.clear(); }); - testWidgets('Initial route can have gaps', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Initial route can have gaps', (WidgetTester tester) async { final GlobalKey keyNav = GlobalKey(); const Key keyRoot = Key('Root'); const Key keyA = Key('A'); @@ -1745,7 +1747,7 @@ void main() { expect(find.byKey(keyABC, skipOffstage: false), findsNothing); }); - testWidgets('The full initial route has to be matched', (WidgetTester tester) async { + testWidgetsWithLeakTracking('The full initial route has to be matched', (WidgetTester tester) async { final GlobalKey keyNav = GlobalKey(); const Key keyRoot = Key('Root'); const Key keyA = Key('A'); @@ -1775,7 +1777,7 @@ void main() { expect(find.byKey(keyAB), findsNothing); }); - testWidgets("Popping immediately after pushing doesn't crash", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Popping immediately after pushing doesn't crash", (WidgetTester tester) async { // Added this test to protect against regression of https://github.com/flutter/flutter/issues/45539 final Map routes = { '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { @@ -1824,7 +1826,7 @@ void main() { }); group('error control test', () { - testWidgets('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async { final GlobalKey navigatorKey = GlobalKey(); await tester.pumpWidget(Navigator( key: navigatorKey, @@ -1850,7 +1852,7 @@ void main() { ); }); - testWidgets('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async { final GlobalKey navigatorKey = GlobalKey(); await tester.pumpWidget(Navigator( key: navigatorKey, @@ -1877,7 +1879,7 @@ void main() { }); }); - testWidgets('OverlayEntry of topmost initial route is marked as opaque', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntry of topmost initial route is marked as opaque', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/38038. final Key root = UniqueKey(); @@ -1901,7 +1903,7 @@ void main() { expect(find.byKey(topmost), findsOneWidget); }); - testWidgets('OverlayEntry of topmost route is set to opaque after Push', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntry of topmost route is set to opaque after Push', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/38038. final GlobalKey navigator = GlobalKey(); @@ -1928,7 +1930,7 @@ void main() { expect(find.byKey(const ValueKey('/A')), findsOneWidget); }); - testWidgets('OverlayEntry of topmost route is set to opaque after Replace', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntry of topmost route is set to opaque after Replace', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/38038. final GlobalKey navigator = GlobalKey(); @@ -1973,7 +1975,7 @@ void main() { expect(find.byKey(const ValueKey('/C')), findsOneWidget); }); - testWidgets('Pushing opaque Route does not rebuild routes below', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Pushing opaque Route does not rebuild routes below', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/45797. final GlobalKey navigator = GlobalKey(); @@ -2007,7 +2009,7 @@ void main() { expect(tester.state(find.byKey(topRoute)).rebuildCount, 1); }); - testWidgets('initial routes below opaque route are offstage', (WidgetTester tester) async { + testWidgetsWithLeakTracking('initial routes below opaque route are offstage', (WidgetTester tester) async { final GlobalKey testKey = GlobalKey(); await tester.pumpWidget( TestDependencies( @@ -2048,7 +2050,7 @@ void main() { expect(find.text('+/a/b+'), findsNothing); }); - testWidgets('Can provide custom onGenerateInitialRoutes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can provide custom onGenerateInitialRoutes', (WidgetTester tester) async { bool onGenerateInitialRoutesCalled = false; final GlobalKey testKey = GlobalKey(); await tester.pumpWidget( @@ -2081,7 +2083,7 @@ void main() { expect(find.text('World'), findsNothing); }); - testWidgets('Navigator.of able to handle input context is a navigator context', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.of able to handle input context is a navigator context', (WidgetTester tester) async { final GlobalKey testKey = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -2094,7 +2096,7 @@ void main() { expect(state, testKey.currentState); }); - testWidgets('Navigator.of able to handle input context is a navigator context - root navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.of able to handle input context is a navigator context - root navigator', (WidgetTester tester) async { final GlobalKey root = GlobalKey(); final GlobalKey sub = GlobalKey(); await tester.pumpWidget( @@ -2116,7 +2118,7 @@ void main() { expect(state, root.currentState); }); - testWidgets('Navigator.maybeOf throws when there is no navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.maybeOf throws when there is no navigator', (WidgetTester tester) async { final GlobalKey testKey = GlobalKey(); await tester.pumpWidget(SizedBox(key: testKey)); @@ -2125,7 +2127,7 @@ void main() { }, throwsFlutterError); }); - testWidgets('Navigator.maybeOf works when there is no navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.maybeOf works when there is no navigator', (WidgetTester tester) async { final GlobalKey testKey = GlobalKey(); await tester.pumpWidget(SizedBox(key: testKey)); @@ -2133,7 +2135,7 @@ void main() { expect(state, isNull); }); - testWidgets('Navigator.maybeOf able to handle input context is a navigator context', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.maybeOf able to handle input context is a navigator context', (WidgetTester tester) async { final GlobalKey testKey = GlobalKey(); await tester.pumpWidget( MaterialApp( @@ -2147,7 +2149,7 @@ void main() { expect(state, testKey.currentState); }); - testWidgets('Navigator.maybeOf able to handle input context is a navigator context - root navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator.maybeOf able to handle input context is a navigator context - root navigator', (WidgetTester tester) async { final GlobalKey root = GlobalKey(); final GlobalKey sub = GlobalKey(); await tester.pumpWidget( @@ -2170,7 +2172,7 @@ void main() { expect(state, root.currentState); }); - testWidgets('pushAndRemove until animates the push', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pushAndRemove until animates the push', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/25080. const Duration kFourTenthsOfTheTransitionDuration = Duration(milliseconds: 120); @@ -2252,7 +2254,7 @@ void main() { expect(find.text('Route: 4', skipOffstage: false), findsNothing); }); - testWidgets('Wrapping TickerMode can turn off ticking in routes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Wrapping TickerMode can turn off ticking in routes', (WidgetTester tester) async { int tickCount = 0; Widget widgetUnderTest({required bool enabled}) { return TickerMode( @@ -2345,7 +2347,7 @@ void main() { expect(popNextOfFirst, secondRoute); }); - testWidgets('hero controller scope works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hero controller scope works', (WidgetTester tester) async { final GlobalKey top = GlobalKey(); final GlobalKey sub = GlobalKey(); @@ -2415,7 +2417,7 @@ void main() { expect(observations[1].previous, 'top1'); }); - testWidgets('hero controller can correctly transfer subscription - replacing navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hero controller can correctly transfer subscription - replacing navigator', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); final GlobalKey key2 = GlobalKey(); @@ -2484,7 +2486,7 @@ void main() { expect(observations[0].previous, 'navigator2'); }); - testWidgets('hero controller can correctly transfer subscription - swapping navigator', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hero controller can correctly transfer subscription - swapping navigator', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); final GlobalKey key2 = GlobalKey(); @@ -2624,7 +2626,7 @@ void main() { expect(observations2[1].previous, 'navigator1'); }); - testWidgets('hero controller subscribes to multiple navigators does throw', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hero controller subscribes to multiple navigators does throw', (WidgetTester tester) async { final HeroControllerSpy spy = HeroControllerSpy(); await tester.pumpWidget( HeroControllerScope( @@ -2662,7 +2664,7 @@ void main() { expect(tester.takeException(), isAssertionError); }); - testWidgets('hero controller throws has correct error message', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hero controller throws has correct error message', (WidgetTester tester) async { final HeroControllerSpy spy = HeroControllerSpy(); await tester.pumpWidget( HeroControllerScope( @@ -2746,7 +2748,7 @@ void main() { ); } - testWidgets('can initialize with pages list', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can initialize with pages list', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); final List myPages = [ const TestPage(key: ValueKey('1'), name:'initial'), @@ -2784,7 +2786,7 @@ void main() { expect(find.text('initial'), findsOneWidget); }); - testWidgets('can handle duplicate page key if update before transition finishes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can handle duplicate page key if update before transition finishes', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/97363. final GlobalKey navigator = GlobalKey(); final List myPages1 = [ @@ -2840,7 +2842,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('throw if onPopPage callback is not provided', (WidgetTester tester) async { + testWidgetsWithLeakTracking('throw if onPopPage callback is not provided', (WidgetTester tester) async { final List myPages = [ const TestPage(key: ValueKey('1'), name:'initial'), const TestPage(key: ValueKey('2'), name:'second'), @@ -2910,7 +2912,7 @@ void main() { ); }); - testWidgets('Can pop route with local history entries using page api', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can pop route with local history entries using page api', (WidgetTester tester) async { List> myPages = const >[ MaterialPage(child: Text('page1')), MaterialPage(child: Text('page2')), @@ -2961,7 +2963,7 @@ void main() { expect(entryRemoved, isTrue); }); - testWidgets('ModalRoute must comply with willHandlePopInternally when there is a PopScope', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ModalRoute must comply with willHandlePopInternally when there is a PopScope', (WidgetTester tester) async { const List> myPages = >[ MaterialPage(child: Text('page1')), MaterialPage( @@ -2994,7 +2996,7 @@ void main() { expect(route.didPop(null), true); }); - testWidgets('can push and pop pages using page api', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can push and pop pages using page api', (WidgetTester tester) async { late Animation secondaryAnimationOfRouteOne; late Animation primaryAnimationOfRouteOne; late Animation secondaryAnimationOfRouteTwo; @@ -3143,7 +3145,7 @@ void main() { expect(primaryAnimationOfRouteThree.status, AnimationStatus.dismissed); }); - testWidgets('can modify routes history and secondary animation still works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can modify routes history and secondary animation still works', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); late Animation secondaryAnimationOfRouteOne; late Animation primaryAnimationOfRouteOne; @@ -3254,7 +3256,7 @@ void main() { expect(primaryAnimationOfRouteOne.status, AnimationStatus.dismissed); }); - testWidgets('Pop no animation page does not crash', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Pop no animation page does not crash', (WidgetTester tester) async { // Regression Test for https://github.com/flutter/flutter/issues/86604. Widget buildNavigator(bool secondPage) { return TestDependencies( @@ -3279,7 +3281,7 @@ void main() { expect(find.text('page1'), findsOneWidget); }); - testWidgets('can work with pageless route', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can work with pageless route', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List myPages = [ const TestPage(key: ValueKey('1'), name:'initial'), @@ -3427,7 +3429,7 @@ void main() { expect(myPages.length, 1); }); - testWidgets('complex case 1', (WidgetTester tester) async { + testWidgetsWithLeakTracking('complex case 1', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List myPages = [ const TestPage(key: ValueKey('1'), name: 'initial'), @@ -3564,7 +3566,7 @@ void main() { }); //Regression test for https://github.com/flutter/flutter/issues/115887 - testWidgets('Complex case 2', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Complex case 2', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List myPages = [ const TestPage(key: ValueKey('1'), name:'initial'), @@ -3628,7 +3630,7 @@ void main() { expect(find.text('second-pageless1'), findsNothing); }); - testWidgets('complex case 1 - with always remove transition delegate', (WidgetTester tester) async { + testWidgetsWithLeakTracking('complex case 1 - with always remove transition delegate', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); final AlwaysRemoveTransitionDelegate transitionDelegate = AlwaysRemoveTransitionDelegate(); List myPages = [ @@ -3773,7 +3775,7 @@ void main() { expect(find.text('forth'), findsOneWidget); }); - testWidgets('can repush a page that was previously popped before it has finished popping', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can repush a page that was previously popped before it has finished popping', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List> myPages = [ const TestPage(key: ValueKey('1'), name: 'initial'), @@ -3825,7 +3827,7 @@ void main() { expect(find.text('second'), findsOneWidget); }); - testWidgets('can update pages before a route has finished popping', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can update pages before a route has finished popping', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List> myPages = [ const TestPage(key: ValueKey('1'), name: 'initial'), @@ -3876,7 +3878,7 @@ void main() { expect(find.text('initial'), findsOneWidget); }); - testWidgets('can update pages before a pageless route has finished popping', (WidgetTester tester) async { + testWidgetsWithLeakTracking('can update pages before a pageless route has finished popping', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/68162. final GlobalKey navigator = GlobalKey(); List> myPages = [ @@ -3923,7 +3925,7 @@ void main() { expect(find.text('initial'), findsOneWidget); }); - testWidgets('pages remove and add trigger observer in the right order', (WidgetTester tester) async { + testWidgetsWithLeakTracking('pages remove and add trigger observer in the right order', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List myPages = [ const TestPage(key: ValueKey('1'), name:'first'), @@ -4016,7 +4018,7 @@ void main() { }); }); - testWidgets('Can reuse NavigatorObserver in rebuilt tree', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can reuse NavigatorObserver in rebuilt tree', (WidgetTester tester) async { final NavigatorObserver observer = NavigatorObserver(); Widget build([Key? key]) { return TestDependencies( @@ -4051,7 +4053,7 @@ void main() { expect(observer.navigator, tester.state(find.byType(Navigator))); }); - testWidgets('Navigator requests focus if requestFocus is true', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator requests focus if requestFocus is true', (WidgetTester tester) async { final GlobalKey navigatorKey = GlobalKey(); final GlobalKey innerKey = GlobalKey(); final Map routes = { @@ -4060,6 +4062,7 @@ void main() { }; late final NavigatorState navigator = navigatorKey.currentState! as NavigatorState; final FocusScopeNode focusNode = FocusScopeNode(); + addTearDown(focusNode.dispose); await tester.pumpWidget(Column( children: [ @@ -4126,7 +4129,7 @@ void main() { expect(focusNode.hasFocus, true); }); - testWidgets('Navigator does not request focus if requestFocus is false', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator does not request focus if requestFocus is false', (WidgetTester tester) async { final GlobalKey navigatorKey = GlobalKey(); final GlobalKey innerKey = GlobalKey(); final Map routes = { @@ -4136,6 +4139,7 @@ void main() { late final NavigatorState navigator = navigatorKey.currentState! as NavigatorState; final FocusScopeNode focusNode = FocusScopeNode(); + addTearDown(focusNode.dispose); await tester.pumpWidget(Column( children: [ @@ -4192,7 +4196,7 @@ void main() { expect(focusNode.hasFocus, true); }); - testWidgets('class implementing NavigatorObserver can be used without problems', (WidgetTester tester) async { + testWidgetsWithLeakTracking('class implementing NavigatorObserver can be used without problems', (WidgetTester tester) async { final _MockNavigatorObserver observer = _MockNavigatorObserver(); Widget build([Key? key]) { return TestDependencies( @@ -4224,7 +4228,7 @@ void main() { observer._checkInvocations([#navigator, #navigator]); }); - testWidgets("Navigator doesn't override FocusTraversalPolicy of ancestors", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Navigator doesn't override FocusTraversalPolicy of ancestors", (WidgetTester tester) async { FocusTraversalPolicy? policy; await tester.pumpWidget( TestDependencies( @@ -4247,7 +4251,7 @@ void main() { expect(policy, isA()); }); - testWidgets('Navigator inserts ReadingOrderTraversalPolicy if no ancestor has a policy', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Navigator inserts ReadingOrderTraversalPolicy if no ancestor has a policy', (WidgetTester tester) async { FocusTraversalPolicy? policy; await tester.pumpWidget( TestDependencies( @@ -4302,7 +4306,7 @@ void main() { .setMockMethodCallHandler(SystemChannels.platform, null); }); - testWidgets('a single route is already defaulted to false', (WidgetTester tester) async { + testWidgetsWithLeakTracking('a single route is already defaulted to false', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( @@ -4317,7 +4321,7 @@ void main() { skip: isBrowser, // [intended] only non-web Android supports predictive back. ); - testWidgets('navigating around a single Navigator with .pop', (WidgetTester tester) async { + testWidgetsWithLeakTracking('navigating around a single Navigator with .pop', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( initialRoute: '/', @@ -4394,7 +4398,7 @@ void main() { skip: isBrowser, // [intended] only non-web Android supports predictive back. ); - testWidgets('navigating around a single Navigator with system back', (WidgetTester tester) async { + testWidgetsWithLeakTracking('navigating around a single Navigator with system back', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( initialRoute: '/', @@ -4471,7 +4475,7 @@ void main() { skip: isBrowser, // [intended] only non-web Android supports predictive back. ); - testWidgets('a single Navigator with a PopScope that defaults to enabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('a single Navigator with a PopScope that defaults to enabled', (WidgetTester tester) async { bool canPop = true; late StateSetter setState; await tester.pumpWidget( @@ -4511,7 +4515,7 @@ void main() { skip: isBrowser, // [intended] only non-web Android supports predictive back. ); - testWidgets('a single Navigator with a PopScope that defaults to disabled', (WidgetTester tester) async { + testWidgetsWithLeakTracking('a single Navigator with a PopScope that defaults to disabled', (WidgetTester tester) async { bool canPop = false; late StateSetter setState; await tester.pumpWidget( @@ -4553,7 +4557,7 @@ void main() { // Test both system back gestures and Navigator.pop. for (final _BackType backType in _BackType.values) { - testWidgets('navigating around nested Navigators', (WidgetTester tester) async { + testWidgetsWithLeakTracking('navigating around nested Navigators', (WidgetTester tester) async { final GlobalKey nav = GlobalKey(); final GlobalKey nestedNav = GlobalKey(); Future goBack() async { @@ -4653,7 +4657,7 @@ void main() { ); } - testWidgets('nested Navigators with a nested PopScope', (WidgetTester tester) async { + testWidgetsWithLeakTracking('nested Navigators with a nested PopScope', (WidgetTester tester) async { bool canPop = true; late StateSetter setState; await tester.pumpWidget( @@ -4780,7 +4784,7 @@ void main() { ); group('Navigator page API', () { - testWidgets('starting with one route as usual', (WidgetTester tester) async { + testWidgetsWithLeakTracking('starting with one route as usual', (WidgetTester tester) async { late StateSetter builderSetState; final List<_Page> pages = <_Page>[_Page.home]; bool canPop() => pages.length <= 1; @@ -4898,7 +4902,7 @@ void main() { skip: isBrowser, // [intended] only non-web Android supports predictive back. ); - testWidgets('starting with existing route history', (WidgetTester tester) async { + testWidgetsWithLeakTracking('starting with existing route history', (WidgetTester tester) async { final List<_Page> pages = <_Page>[_Page.home, _Page.one]; bool canPop() => pages.length <= 1; diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart index 66663e061adb6..76dbf6d726d49 100644 --- a/packages/flutter/test/widgets/overlay_test.dart +++ b/packages/flutter/test/widgets/overlay_test.dart @@ -5,20 +5,26 @@ 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'; import 'semantics_tester.dart'; void main() { - testWidgets('OverflowEntries context contains Overlay', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverflowEntries context contains Overlay', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); bool didBuild = false; + late final OverlayEntry overlayEntry1; + addTearDown(() => overlayEntry1..remove()..dispose()); + late final OverlayEntry overlayEntry2; + addTearDown(() => overlayEntry2..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + overlayEntry1 = OverlayEntry( builder: (BuildContext context) { didBuild = true; final Overlay overlay = context.findAncestorWidgetOfExactType()!; @@ -26,7 +32,7 @@ void main() { return Container(); }, ), - OverlayEntry( + overlayEntry2 = OverlayEntry( builder: (BuildContext context) => Container(), ), ], @@ -80,25 +86,32 @@ void main() { ); }); - testWidgets('Offstage overlay', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Offstage overlay', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); + late final OverlayEntry overlayEntry1; + addTearDown(() => overlayEntry1..remove()..dispose()); + late final OverlayEntry overlayEntry2; + addTearDown(() => overlayEntry2..remove()..dispose()); + late final OverlayEntry overlayEntry3; + addTearDown(() => overlayEntry3..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + overlayEntry1 = OverlayEntry( opaque: true, maintainState: true, builder: (BuildContext context) => Container(), ), - OverlayEntry( + overlayEntry2 = OverlayEntry( opaque: true, maintainState: true, builder: (BuildContext context) => Container(), ), - OverlayEntry( + overlayEntry3 = OverlayEntry( opaque: true, maintainState: true, builder: (BuildContext context) => Container(), @@ -163,16 +176,19 @@ void main() { ); }); - testWidgets('insert top', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insert top', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); @@ -187,8 +203,10 @@ void main() { buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); overlay.insert( - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('New'); return Container(); @@ -200,17 +218,19 @@ void main() { expect(buildOrder, ['Base', 'New']); }); - testWidgets('insert below', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insert below', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); - OverlayEntry base; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); final List buildOrder = []; + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - base = OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); @@ -225,37 +245,43 @@ void main() { buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); overlay.insert( - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('New'); return Container(); }, ), - below: base, + below: baseEntry, ); await tester.pump(); expect(buildOrder, ['New', 'Base']); }); - testWidgets('insert above', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insert above', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); - OverlayEntry base; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + late final OverlayEntry topEntry; + addTearDown(() => topEntry..remove()..dispose()); final List buildOrder = []; + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - base = OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); }, ), - OverlayEntry( + topEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Top'); return Container(); @@ -270,30 +296,35 @@ void main() { buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); overlay.insert( - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('New'); return Container(); }, ), - above: base, + above: baseEntry, ); await tester.pump(); expect(buildOrder, ['Base', 'New', 'Top']); }); - testWidgets('insertAll top', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insertAll top', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); @@ -320,6 +351,11 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in entries) { + entry..remove()..dispose(); + } + }); buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; @@ -329,17 +365,19 @@ void main() { expect(buildOrder, ['Base', 'New1', 'New2']); }); - testWidgets('insertAll below', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insertAll below', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); - OverlayEntry base; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); final List buildOrder = []; + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - base = OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); @@ -366,32 +404,41 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in entries) { + entry..remove()..dispose(); + } + }); buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; - overlay.insertAll(entries, below: base); + overlay.insertAll(entries, below: baseEntry); await tester.pump(); expect(buildOrder, ['New1', 'New2','Base']); }); - testWidgets('insertAll above', (WidgetTester tester) async { + testWidgetsWithLeakTracking('insertAll above', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; - OverlayEntry base; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + late final OverlayEntry topEntry; + addTearDown(() => topEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - base = OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Base'); return Container(); }, ), - OverlayEntry( + topEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add('Top'); return Container(); @@ -418,16 +465,21 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in entries) { + entry..remove()..dispose(); + } + }); buildOrder.clear(); final OverlayState overlay = overlayKey.currentState! as OverlayState; - overlay.insertAll(entries, above: base); + overlay.insertAll(entries, above: baseEntry); await tester.pump(); expect(buildOrder, ['Base', 'New1', 'New2', 'Top']); }); - testWidgets('rearrange', (WidgetTester tester) async { + testWidgetsWithLeakTracking('rearrange', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; final List initialEntries = [ @@ -456,6 +508,11 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in initialEntries) { + entry..remove()..dispose(); + } + }); await tester.pumpWidget( Directionality( @@ -469,9 +526,11 @@ void main() { expect(buildOrder, [0, 1, 2, 3]); + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); final List rearranged = [ initialEntries[3], - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add(4); return Container(); @@ -490,9 +549,10 @@ void main() { expect(buildOrder, [3, 4, 2, 0, 1]); }); - testWidgets('rearrange above', (WidgetTester tester) async { + testWidgetsWithLeakTracking('rearrange above', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; + final List initialEntries = [ OverlayEntry( builder: (BuildContext context) { @@ -519,6 +579,11 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in initialEntries) { + entry..remove()..dispose(); + } + }); await tester.pumpWidget( Directionality( @@ -532,9 +597,11 @@ void main() { expect(buildOrder, [0, 1, 2, 3]); + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); final List rearranged = [ initialEntries[3], - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add(4); return Container(); @@ -553,7 +620,7 @@ void main() { expect(buildOrder, [3, 4, 2, 1, 0]); }); - testWidgets('rearrange below', (WidgetTester tester) async { + testWidgetsWithLeakTracking('rearrange below', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final List buildOrder = []; final List initialEntries = [ @@ -582,6 +649,11 @@ void main() { }, ), ]; + addTearDown(() { + for (final OverlayEntry entry in initialEntries) { + entry..remove()..dispose(); + } + }); await tester.pumpWidget( Directionality( @@ -595,9 +667,11 @@ void main() { expect(buildOrder, [0, 1, 2, 3]); + late final OverlayEntry newEntry; + addTearDown(() => newEntry..remove()..dispose()); final List rearranged = [ initialEntries[3], - OverlayEntry( + newEntry = OverlayEntry( builder: (BuildContext context) { buildOrder.add(4); return Container(); @@ -694,7 +768,7 @@ void main() { await tester.pump(); }); - testWidgets('OverlayState.of() throws when called if an Overlay does not exist', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayState.of() throws when called if an Overlay does not exist', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -740,9 +814,11 @@ void main() { ); }); - testWidgets("OverlayState.maybeOf() works when an Overlay does and doesn't exist", (WidgetTester tester) async { + testWidgetsWithLeakTracking("OverlayState.maybeOf() works when an Overlay does and doesn't exist", (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); OverlayState? foundState; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); await tester.pumpWidget( Directionality( @@ -750,7 +826,7 @@ void main() { child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { foundState = Overlay.maybeOf(context); return Container(); @@ -781,7 +857,7 @@ void main() { expect(foundState, isNull); }); - testWidgets('OverlayEntry.opaque can be changed when OverlayEntry is not part of an Overlay (yet)', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntry.opaque can be changed when OverlayEntry is not part of an Overlay (yet)', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); final Key root = UniqueKey(); final Key top = UniqueKey(); @@ -790,6 +866,7 @@ void main() { return Container(key: root); }, ); + addTearDown(() => rootEntry..remove()..dispose()); await tester.pumpWidget( Directionality( @@ -810,6 +887,7 @@ void main() { return Container(key: top); }, ); + addTearDown(() => newEntry..remove()..dispose()); expect(newEntry.opaque, isFalse); newEntry.opaque = true; // Does neither trigger an assert nor throw. expect(newEntry.opaque, isTrue); @@ -822,7 +900,7 @@ void main() { expect(find.byKey(top), findsOneWidget); }); - testWidgets('OverlayEntries do not rebuild when opaqueness changes', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntries do not rebuild when opaqueness changes', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/45797. final GlobalKey overlayKey = GlobalKey(); @@ -839,18 +917,21 @@ void main() { return bottomWidget; }, ); + addTearDown(() => bottomEntry..remove()..dispose()); final OverlayEntry middleEntry = OverlayEntry( maintainState: true, builder: (BuildContext context) { return middleWidget; }, ); + addTearDown(() => middleEntry..remove()..dispose()); final OverlayEntry topEntry = OverlayEntry( maintainState: true, builder: (BuildContext context) { return topWidget; }, ); + addTearDown(() => topEntry..remove()..dispose()); await tester.pumpWidget( Directionality( @@ -881,7 +962,7 @@ void main() { expect(tester.state(find.byKey(top)).rebuildCount, 1); }); - testWidgets('OverlayEntries do not rebuild when opaque entry is added', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntries do not rebuild when opaque entry is added', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/45797. final GlobalKey overlayKey = GlobalKey(); @@ -898,6 +979,7 @@ void main() { return bottomWidget; }, ); + addTearDown(() => bottomEntry..remove()..dispose()); final OverlayEntry middleEntry = OverlayEntry( opaque: true, maintainState: true, @@ -905,12 +987,14 @@ void main() { return middleWidget; }, ); + addTearDown(() => middleEntry..remove()..dispose()); final OverlayEntry topEntry = OverlayEntry( maintainState: true, builder: (BuildContext context) { return topWidget; }, ); + addTearDown(() => topEntry..remove()..dispose()); await tester.pumpWidget( Directionality( @@ -941,16 +1025,19 @@ void main() { expect(tester.state(find.byKey(top)).rebuildCount, 1); }); - testWidgets('entries below opaque entries are ignored for hit testing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('entries below opaque entries are ignored for hit testing', (WidgetTester tester) async { final GlobalKey overlayKey = GlobalKey(); int bottomTapCount = 0; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( maintainState: true, builder: (BuildContext context) { return GestureDetector( @@ -969,13 +1056,17 @@ void main() { await tester.tap(find.byKey(overlayKey), warnIfMissed: false); // gesture detector is translucent; no hit is registered between it and the render view expect(bottomTapCount, 1); - overlayKey.currentState!.insert(OverlayEntry( - maintainState: true, - opaque: true, - builder: (BuildContext context) { - return Container(); - }, - )); + late final OverlayEntry newEntry1; + addTearDown(() => newEntry1..remove()..dispose()); + overlayKey.currentState!.insert( + newEntry1 = OverlayEntry( + maintainState: true, + opaque: true, + builder: (BuildContext context) { + return Container(); + }, + ), + ); await tester.pump(); // Bottom is offstage and does not receive tap events. @@ -985,17 +1076,21 @@ void main() { expect(bottomTapCount, 1); int topTapCount = 0; - overlayKey.currentState!.insert(OverlayEntry( - maintainState: true, - opaque: true, - builder: (BuildContext context) { - return GestureDetector( - onTap: () { - topTapCount++; - }, - ); - }, - )); + late final OverlayEntry newEntry2; + addTearDown(() => newEntry2..remove()..dispose()); + overlayKey.currentState!.insert( + newEntry2 = OverlayEntry( + maintainState: true, + opaque: true, + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + topTapCount++; + }, + ); + }, + ), + ); await tester.pump(); expect(topTapCount, 0); @@ -1004,22 +1099,27 @@ void main() { expect(bottomTapCount, 1); }); - testWidgets('Semantics of entries below opaque entries are ignored', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Semantics of entries below opaque entries are ignored', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey overlayKey = GlobalKey(); + late final OverlayEntry bottomEntry; + addTearDown(() => bottomEntry..remove()..dispose()); + late final OverlayEntry topEntry; + addTearDown(() => topEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( key: overlayKey, initialEntries: [ - OverlayEntry( + bottomEntry = OverlayEntry( maintainState: true, builder: (BuildContext context) { return const Text('bottom'); }, ), - OverlayEntry( + topEntry = OverlayEntry( maintainState: true, opaque: true, builder: (BuildContext context) { @@ -1039,13 +1139,16 @@ void main() { semantics.dispose(); }); - testWidgets('Can use Positioned within OverlayEntry', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Can use Positioned within OverlayEntry', (WidgetTester tester) async { + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { return const Positioned( left: 145, @@ -1117,13 +1220,16 @@ void main() { } }); - testWidgets('Overlay always applies clip', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Overlay always applies clip', (WidgetTester tester) async { + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) => Positioned(left: 10, right: 10, child: Container()), ), ], @@ -1139,7 +1245,7 @@ void main() { ); }); - testWidgets('OverlayEntry throws if inserted to an invalid Overlay', (WidgetTester tester) async { + testWidgetsWithLeakTracking('OverlayEntry throws if inserted to an invalid Overlay', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, @@ -1148,6 +1254,7 @@ void main() { ); final OverlayState overlay = tester.state(find.byType(Overlay)); final OverlayEntry entry = OverlayEntry(builder: (BuildContext context) => const SizedBox()); + addTearDown(() => entry..remove()..dispose()); expect( () => overlay.insert(entry), returnsNormally, @@ -1212,13 +1319,14 @@ void main() { child: Overlay(key: overlayKey), ); - testWidgets('mounted state can be listened', (WidgetTester tester) async { + testWidgetsWithLeakTracking('mounted state can be listened', (WidgetTester tester) async { await tester.pumpWidget(emptyOverlay); final OverlayState overlay = overlayKey.currentState! as OverlayState; final List mountedLog = []; final OverlayEntry entry = OverlayEntry( builder: (BuildContext context) => Container(), ); + addTearDown(entry.dispose); entry.addListener(() { mountedLog.add(entry.mounted); @@ -1245,12 +1353,13 @@ void main() { expect(mountedLog, [true, false, true, false]); }); - testWidgets('throw if disposed before removal', (WidgetTester tester) async { + testWidgetsWithLeakTracking('throw if disposed before removal', (WidgetTester tester) async { await tester.pumpWidget(emptyOverlay); final OverlayState overlay = overlayKey.currentState! as OverlayState; final OverlayEntry entry = OverlayEntry( builder: (BuildContext context) => Container(), ); + addTearDown(() => entry..remove()..dispose()); overlay.insert(entry); Object? error; @@ -1279,7 +1388,7 @@ void main() { expect(error, isAssertionError); }); - testWidgets('delayed dispose', (WidgetTester tester) async { + testWidgetsWithLeakTracking('delayed dispose', (WidgetTester tester) async { await tester.pumpWidget(emptyOverlay); final OverlayState overlay = overlayKey.currentState! as OverlayState; final List mountedLog = []; @@ -1315,15 +1424,17 @@ void main() { }); group('LookupBoundary', () { - testWidgets('hides Overlay from Overlay.maybeOf', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hides Overlay from Overlay.maybeOf', (WidgetTester tester) async { OverlayState? overlay; + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { return LookupBoundary( child: Builder( @@ -1343,13 +1454,16 @@ void main() { expect(overlay, isNull); }); - testWidgets('hides Overlay from Overlay.of', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hides Overlay from Overlay.of', (WidgetTester tester) async { + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { return LookupBoundary( child: Builder( @@ -1386,13 +1500,16 @@ void main() { ); }); - testWidgets('hides Overlay from debugCheckHasOverlay', (WidgetTester tester) async { + testWidgetsWithLeakTracking('hides Overlay from debugCheckHasOverlay', (WidgetTester tester) async { + late final OverlayEntry baseEntry; + addTearDown(() => baseEntry..remove()..dispose()); + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: [ - OverlayEntry( + baseEntry = OverlayEntry( builder: (BuildContext context) { return LookupBoundary( child: Builder( diff --git a/packages/flutter/test/widgets/router_restoration_test.dart b/packages/flutter/test/widgets/router_restoration_test.dart index 4d429c893d1b0..109dee1c0343f 100644 --- a/packages/flutter/test/widgets/router_restoration_test.dart +++ b/packages/flutter/test/widgets/router_restoration_test.dart @@ -5,9 +5,10 @@ import 'package:flutter/foundation.dart'; 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('Router state restoration without RouteInformationProvider', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Router state restoration without RouteInformationProvider', (WidgetTester tester) async { final UniqueKey router = UniqueKey(); _TestRouterDelegate delegate() => tester.widget>(find.byKey(router)).routerDelegate as _TestRouterDelegate; @@ -39,7 +40,12 @@ void main() { expect(find.text('Current config: /foo'), findsOneWidget); expect(delegate().newRoutePaths, isEmpty); expect(delegate().restoredRoutePaths, ['/foo', '/foo']); - }); + }, + leakTrackingTestConfig: const LeakTrackingTestConfig( + // TODO(ksokolovskyi): remove after fixing + // https://github.com/flutter/flutter/issues/134205 + notDisposedAllowList: {'_RestorableRouteInformation': 2}, + )); testWidgets('Router state restoration with RouteInformationProvider', (WidgetTester tester) async { final UniqueKey router = UniqueKey(); @@ -152,22 +158,37 @@ class _TestRouteInformationProvider extends RouteInformationProvider with Change } } -class _TestWidget extends StatelessWidget { +class _TestWidget extends StatefulWidget { const _TestWidget({this.withInformationProvider = false, this.routerKey}); final bool withInformationProvider; final Key? routerKey; + @override + State<_TestWidget> createState() => _TestWidgetState(); +} + +class _TestWidgetState extends State<_TestWidget> { + final _TestRouterDelegate _delegate = _TestRouterDelegate(); + final _TestRouteInformationProvider _routeInformationProvider = _TestRouteInformationProvider(); + + @override + void dispose() { + _delegate.dispose(); + _routeInformationProvider.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return RootRestorationScope( restorationId: 'root', child: Router( - key: routerKey, + key: widget.routerKey, restorationScopeId: 'router', - routerDelegate: _TestRouterDelegate(), + routerDelegate: _delegate, routeInformationParser: _TestRouteInformationParser(), - routeInformationProvider: withInformationProvider ? _TestRouteInformationProvider() : null, + routeInformationProvider: widget.withInformationProvider ? _routeInformationProvider : null, ), ); } diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index e7576552f6b24..14d6ba63ecf54 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -861,12 +861,13 @@ void main() { expect(targetMidLeftPage1, findsOneWidget); }); - testWidgets('ensureVisible does not move TabViews', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ensureVisible does not move TabViews', (WidgetTester tester) async { final TickerProvider vsync = TestTickerProvider(); final TabController controller = TabController( length: 3, vsync: vsync, ); + addTearDown(controller.dispose); await tester.pumpWidget( Directionality( diff --git a/packages/flutter/test/widgets/transform_test.dart b/packages/flutter/test/widgets/transform_test.dart index 2165e0a8e1054..4accf2b495e10 100644 --- a/packages/flutter/test/widgets/transform_test.dart +++ b/packages/flutter/test/widgets/transform_test.dart @@ -13,10 +13,11 @@ import 'dart:ui' as ui; 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'; import 'package:vector_math/vector_math_64.dart'; void main() { - testWidgets('Transform origin', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform origin', (WidgetTester tester) async { bool didReceiveTap = false; await tester.pumpWidget( Directionality( @@ -64,7 +65,7 @@ void main() { expect(didReceiveTap, isTrue); }); - testWidgets('Transform alignment', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform alignment', (WidgetTester tester) async { bool didReceiveTap = false; await tester.pumpWidget( Directionality( @@ -112,7 +113,7 @@ void main() { expect(didReceiveTap, isTrue); }); - testWidgets('Transform AlignmentDirectional alignment', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform AlignmentDirectional alignment', (WidgetTester tester) async { bool didReceiveTap = false; Widget buildFrame(TextDirection textDirection, AlignmentGeometry alignment) { @@ -183,7 +184,7 @@ void main() { expect(didReceiveTap, isTrue); }); - testWidgets('Transform offset + alignment', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform offset + alignment', (WidgetTester tester) async { bool didReceiveTap = false; await tester.pumpWidget( Directionality( @@ -232,7 +233,7 @@ void main() { expect(didReceiveTap, isTrue); }); - testWidgets('Composited transform offset', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Composited transform offset', (WidgetTester tester) async { await tester.pumpWidget( Center( child: SizedBox( @@ -261,7 +262,7 @@ void main() { expect(transform.getTranslation(), equals(Vector3(100.0, 75.0, 0.0))); }); - testWidgets('Transform.rotate', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.rotate', (WidgetTester tester) async { await tester.pumpWidget( Transform.rotate( angle: math.pi / 2.0, @@ -283,7 +284,7 @@ void main() { ]); }); - testWidgets('applyPaintTransform of Transform in Padding', (WidgetTester tester) async { + testWidgetsWithLeakTracking('applyPaintTransform of Transform in Padding', (WidgetTester tester) async { await tester.pumpWidget( Padding( padding: const EdgeInsets.only( @@ -301,7 +302,7 @@ void main() { expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(30.0, 20.0)); }); - testWidgets('Transform.translate', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.translate', (WidgetTester tester) async { await tester.pumpWidget( Transform.translate( offset: const Offset(100.0, 50.0), @@ -316,7 +317,7 @@ void main() { expect(tester.getTopLeft(find.byType(Container)), const Offset(100.0, 50.0)); }); - testWidgets('Transform.scale', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.scale', (WidgetTester tester) async { await tester.pumpWidget( Transform.scale( scale: 2.0, @@ -339,7 +340,7 @@ void main() { ]); }); - testWidgets('Transform with nan value short-circuits rendering', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform with nan value short-circuits rendering', (WidgetTester tester) async { await tester.pumpWidget( Transform( transform: Matrix4.identity() @@ -351,7 +352,7 @@ void main() { expect(tester.layers, hasLength(1)); }); - testWidgets('Transform with inf value short-circuits rendering', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform with inf value short-circuits rendering', (WidgetTester tester) async { await tester.pumpWidget( Transform( transform: Matrix4.identity() @@ -363,7 +364,7 @@ void main() { expect(tester.layers, hasLength(1)); }); - testWidgets('Transform with -inf value short-circuits rendering', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform with -inf value short-circuits rendering', (WidgetTester tester) async { await tester.pumpWidget( Transform( transform: Matrix4.identity() @@ -375,7 +376,7 @@ void main() { expect(tester.layers, hasLength(1)); }); - testWidgets('Transform.rotate does not remove layers due to singular short-circuit', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.rotate does not remove layers due to singular short-circuit', (WidgetTester tester) async { await tester.pumpWidget( Transform.rotate( angle: math.pi / 2, @@ -386,7 +387,7 @@ void main() { expect(tester.layers, hasLength(3)); }); - testWidgets('Transform.rotate creates nice rotation matrices for 0, 90, 180, 270 degrees', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.rotate creates nice rotation matrices for 0, 90, 180, 270 degrees', (WidgetTester tester) async { await tester.pumpWidget( Transform.rotate( angle: math.pi / 2, @@ -447,7 +448,7 @@ void main() { expect(tester.layers, hasLength(2)); }); - testWidgets('Transform.scale with 0.0 does not paint child layers', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.scale with 0.0 does not paint child layers', (WidgetTester tester) async { await tester.pumpWidget( Transform.scale( scale: 0.0, @@ -486,7 +487,7 @@ void main() { }); - testWidgets('Translated child into translated box - hit test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Translated child into translated box - hit test', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); bool pointerDown = false; await tester.pumpWidget( @@ -524,7 +525,7 @@ void main() { ); } - testWidgets( + testWidgetsWithLeakTracking( '3D transform renders the same with or without needsCompositing', (WidgetTester tester) async { for (double angle = 0; angle <= math.pi/4; angle += 0.01) { @@ -532,6 +533,7 @@ void main() { final RenderBox renderBox = tester.binding.renderView.child!; final OffsetLayer layer = renderBox.debugLayer! as OffsetLayer; final ui.Image imageWithCompositing = await layer.toImage(renderBox.paintBounds); + addTearDown(imageWithCompositing.dispose); await tester.pumpWidget(RepaintBoundary(child: generateTransform(false, angle))); await expectLater(find.byType(RepaintBoundary).first, matchesReferenceImage(imageWithCompositing)); @@ -545,7 +547,7 @@ void main() { return numbers.map((String str) => double.parse(str.trim())).toList(); } - testWidgets('Transform.translate with FilterQuality produces filter layer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.translate with FilterQuality produces filter layer', (WidgetTester tester) async { await tester.pumpWidget( Transform.translate( offset: const Offset(25.0, 25.0), @@ -563,7 +565,7 @@ void main() { ]); }); - testWidgets('Transform.scale with FilterQuality produces filter layer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.scale with FilterQuality produces filter layer', (WidgetTester tester) async { await tester.pumpWidget( Transform.scale( scale: 3.14159, @@ -581,7 +583,7 @@ void main() { ]); }); - testWidgets('Transform.rotate with FilterQuality produces filter layer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform.rotate with FilterQuality produces filter layer', (WidgetTester tester) async { await tester.pumpWidget( Transform.rotate( angle: math.pi / 4, @@ -599,7 +601,7 @@ void main() { ]); }); - testWidgets('Offset Transform.rotate with FilterQuality produces filter layer', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Offset Transform.rotate with FilterQuality produces filter layer', (WidgetTester tester) async { await tester.pumpWidget( SizedBox(width: 400, height: 400, child: Center( @@ -621,7 +623,7 @@ void main() { ]); }); - testWidgets('Transform layers update to match child and filterQuality', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform layers update to match child and filterQuality', (WidgetTester tester) async { await tester.pumpWidget( Transform.rotate( angle: math.pi / 4, @@ -657,7 +659,7 @@ void main() { expect(tester.layers.whereType(), hasLength(1)); }); - testWidgets('Transform layers with filterQuality golden', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Transform layers with filterQuality golden', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -701,7 +703,7 @@ void main() { ); }); - testWidgets("Transform.scale() does not accept all three 'scale', 'scaleX' and 'scaleY' parameters to be non-null", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Transform.scale() does not accept all three 'scale', 'scaleX' and 'scaleY' parameters to be non-null", (WidgetTester tester) async { await expectLater(() { tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, @@ -719,7 +721,7 @@ void main() { }, throwsAssertionError); }); - testWidgets("Transform.scale() needs at least one of 'scale', 'scaleX' and 'scaleY' to be non-null, otherwise throws AssertionError", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Transform.scale() needs at least one of 'scale', 'scaleX' and 'scaleY' to be non-null, otherwise throws AssertionError", (WidgetTester tester) async { await expectLater(() { tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, @@ -734,7 +736,7 @@ void main() { }, throwsAssertionError); }); - testWidgets("Transform.scale() scales widget uniformly with 'scale' parameter", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Transform.scale() scales widget uniformly with 'scale' parameter", (WidgetTester tester) async { const double scale = 1.5; const double height = 100; const double width = 150; @@ -760,7 +762,7 @@ void main() { expect(tester.getBottomRight(find.byType(Container)), target.bottomRight(tester.getTopLeft(find.byType(Container)))); }); - testWidgets("Transform.scale() scales widget according to 'scaleX' and 'scaleY'", (WidgetTester tester) async { + testWidgetsWithLeakTracking("Transform.scale() scales widget according to 'scaleX' and 'scaleY'", (WidgetTester tester) async { const double scaleX = 1.5; const double scaleY = 1.2; const double height = 100; @@ -788,7 +790,7 @@ void main() { expect(tester.getBottomRight(find.byType(Container)), target.bottomRight(tester.getTopLeft(find.byType(Container)))); }); - testWidgets( + testWidgetsWithLeakTracking( 'Transform.flip does flip child correctly', (WidgetTester tester) async { const Offset topRight = Offset(60, 20);