From 2c3db435a8a80dbdee5f6cb6f0e3a7a2395bc512 Mon Sep 17 00:00:00 2001 From: Kostia Sokolovskyi Date: Sat, 14 Oct 2023 04:58:17 +0200 Subject: [PATCH] _RouterState should dispose created _RestorableRouteInformation. (#136556) ### Description - Fixes https://github.com/flutter/flutter/issues/134205. ### Tests - Removes ignoring the `_RestorableRouteInformation` leak from `cupertino/app_test.dart`; - Removes ignoring the `_RestorableRouteInformation` leak from `material/app_test.dart`; - Removes ignoring the `_RestorableRouteInformation` leak from `widgets/app_test.dart`; - Removes ignoring the `_RestorableRouteInformation` leak from `widgets/route_notification_messages_test.dart`; - Removes ignoring the `_RestorableRouteInformation` leak from `widgets/router_restoration_test.dart`; - Updates `widgets/router_test.dart` to use `testWidgetsWithLeakTracking`. --- packages/flutter/lib/src/widgets/router.dart | 5 + packages/flutter/test/cupertino/app_test.dart | 21 +- packages/flutter/test/material/app_test.dart | 21 +- packages/flutter/test/widgets/app_test.dart | 28 +- .../route_notification_messages_test.dart | 7 +- .../test/widgets/router_restoration_test.dart | 7 +- .../flutter/test/widgets/router_test.dart | 705 ++++++++++-------- 7 files changed, 421 insertions(+), 373 deletions(-) diff --git a/packages/flutter/lib/src/widgets/router.dart b/packages/flutter/lib/src/widgets/router.dart index 04924fcfd8..dc663c4195 100644 --- a/packages/flutter/lib/src/widgets/router.dart +++ b/packages/flutter/lib/src/widgets/router.dart @@ -632,6 +632,10 @@ class _RouterState extends State> with RestorationMixin { } void _reportRouteInformation(Duration timestamp) { + if (!mounted) { + return; + } + assert(_routeInformationReportingTaskScheduled); _routeInformationReportingTaskScheduled = false; @@ -726,6 +730,7 @@ class _RouterState extends State> with RestorationMixin { @override void dispose() { + _routeInformation.dispose(); widget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification); widget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification); widget.routerDelegate.removeListener(_handleRouterDelegateNotification); diff --git a/packages/flutter/test/cupertino/app_test.dart b/packages/flutter/test/cupertino/app_test.dart index ea384af7fe..8ca898e372 100644 --- a/packages/flutter/test/cupertino/app_test.dart +++ b/packages/flutter/test/cupertino/app_test.dart @@ -178,12 +178,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('CupertinoApp.router route information parser is optional', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -209,12 +204,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('CupertinoApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -300,12 +290,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('CupertinoApp has correct default ScrollBehavior', (WidgetTester tester) async { late BuildContext capturedContext; diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart index 2a4110f1d0..19f3923478 100644 --- a/packages/flutter/test/material/app_test.dart +++ b/packages/flutter/test/material/app_test.dart @@ -1114,12 +1114,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('MaterialApp.router route information parser is optional', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -1145,12 +1140,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('MaterialApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -1236,12 +1226,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - // TODO(polina-c): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - leakTrackingTestConfig: const LeakTrackingTestConfig( - allowAllNotDisposed: true, - )); + }); testWidgetsWithLeakTracking('MaterialApp.builder can build app without a Navigator', (WidgetTester tester) async { Widget? builderChild; diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart index 38c6743976..0729e0fe32 100644 --- a/packages/flutter/test/widgets/app_test.dart +++ b/packages/flutter/test/widgets/app_test.dart @@ -304,12 +304,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(ksokolovskyi): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - notDisposedAllowList: {'_RestorableRouteInformation': 1}, - )); + }); testWidgetsWithLeakTracking('WidgetsApp.router route information parser is optional', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -336,12 +331,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(ksokolovskyi): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - notDisposedAllowList: {'_RestorableRouteInformation': 1}, - )); + }); testWidgetsWithLeakTracking('WidgetsApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -432,12 +422,7 @@ void main() { await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); await tester.pumpAndSettle(); expect(find.text('popped'), findsOneWidget); - }, - leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(ksokolovskyi): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - notDisposedAllowList: {'_RestorableRouteInformation': 1}, - )); + }); testWidgetsWithLeakTracking('WidgetsApp.router has correct default', (WidgetTester tester) async { final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( @@ -453,12 +438,7 @@ void main() { color: const Color(0xFF123456), )); expect(find.text('/'), findsOneWidget); - }, - leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(ksokolovskyi): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - notDisposedAllowList: {'_RestorableRouteInformation': 1}, - )); + }); testWidgetsWithLeakTracking('WidgetsApp has correct default ScrollBehavior', (WidgetTester tester) async { late BuildContext capturedContext; diff --git a/packages/flutter/test/widgets/route_notification_messages_test.dart b/packages/flutter/test/widgets/route_notification_messages_test.dart index c1913d7db5..ab8ec033ee 100644 --- a/packages/flutter/test/widgets/route_notification_messages_test.dart +++ b/packages/flutter/test/widgets/route_notification_messages_test.dart @@ -316,12 +316,7 @@ void main() { 'replace': false, }), ]); - }, - leakTrackingTestConfig: const LeakTrackingTestConfig( - // TODO(ksokolovskyi): remove after fixing - // https://github.com/flutter/flutter/issues/134205 - notDisposedAllowList: {'_RestorableRouteInformation': 1}, - )); + }); } typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation); diff --git a/packages/flutter/test/widgets/router_restoration_test.dart b/packages/flutter/test/widgets/router_restoration_test.dart index 109dee1c03..921d20b159 100644 --- a/packages/flutter/test/widgets/router_restoration_test.dart +++ b/packages/flutter/test/widgets/router_restoration_test.dart @@ -40,12 +40,7 @@ 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(); diff --git a/packages/flutter/test/widgets/router_test.dart b/packages/flutter/test/widgets/router_test.dart index 2f739778d5..0f7bd42c06 100644 --- a/packages/flutter/test/widgets/router_test.dart +++ b/packages/flutter/test/widgets/router_test.dart @@ -11,20 +11,24 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { - testWidgets('Simple router basic functionality - synchronized', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Simple router basic functionality - synchronized', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + ); + addTearDown(delegate.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - ), + routerDelegate: delegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -37,8 +41,9 @@ void main() { expect(find.text('update'), findsOneWidget); }); - testWidgets('Simple router basic functionality - asynchronized', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Simple router basic functionality - asynchronized', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -51,6 +56,8 @@ void main() { return Text(information.uri.toString()); }, ); + addTearDown(delegate.dispose); + await tester.runAsync(() async { await tester.pumpWidget(buildBoilerPlate( Router( @@ -81,8 +88,9 @@ void main() { }); }); - testWidgets('Interrupts route parsing should not crash', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Interrupts route parsing should not crash', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -95,6 +103,8 @@ void main() { return Text(information.uri.toString()); }, ); + addTearDown(delegate.dispose); + await tester.runAsync(() async { await tester.pumpWidget(buildBoilerPlate( Router( @@ -130,7 +140,7 @@ void main() { }); }); - testWidgets('Router.maybeOf can be null', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Router.maybeOf can be null', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(buildBoilerPlate( Text('dummy', key: key), @@ -147,28 +157,31 @@ void main() { ); }); - testWidgets('Simple router can handle pop route', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Simple router can handle pop route', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); await tester.pumpWidget(buildBoilerPlate( Router( routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, backButtonDispatcher: dispatcher, ), )); @@ -186,20 +199,24 @@ void main() { expect(find.text('popped'), findsOneWidget); }); - testWidgets('Router throw when passing routeInformationProvider without routeInformationParser', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Router throw when passing routeInformationProvider without routeInformationParser', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + ); + addTearDown(delegate.dispose); + expect( () { Router( routeInformationProvider: provider, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - ), + routerDelegate: delegate, ); }, throwsA(isAssertionError.having( @@ -210,8 +227,9 @@ void main() { ); }); - testWidgets('PopNavigatorRouterDelegateMixin works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('PopNavigatorRouterDelegateMixin works', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -227,6 +245,8 @@ void main() { return route.didPop(result); }, ); + addTearDown(delegate.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( routeInformationProvider: provider, @@ -263,44 +283,50 @@ void main() { expect(find.text('popped'), findsOneWidget); }); - testWidgets('Nested routers back button dispatcher works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Nested routers back button dispatcher works', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate outerDelegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + final BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); + innerDispatcher.takePriority(); + final SimpleRouterDelegate innerDelegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? innerInformation) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(innerDelegate.dispose); + // Creates the sub-router. + return Router( + backButtonDispatcher: innerDispatcher, + routerDelegate: innerDelegate, + ); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped outer'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(outerDelegate.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - final BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); - innerDispatcher.takePriority(); - // Creates the sub-router. - return Router( - backButtonDispatcher: innerDispatcher, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? innerInformation) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner'), - ); - return SynchronousFuture(true); - }, - ), - ); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped outer'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: outerDelegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -313,63 +339,72 @@ void main() { expect(find.text('popped inner'), findsOneWidget); }); - testWidgets('Nested router back button dispatcher works for multiple children', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Nested router back button dispatcher works for multiple children', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); final BackButtonDispatcher innerDispatcher1 = ChildBackButtonDispatcher(outerDispatcher); final BackButtonDispatcher innerDispatcher2 = ChildBackButtonDispatcher(outerDispatcher); + final SimpleRouterDelegate outerDelegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + late final SimpleRouterDelegate innerDelegate1; + addTearDown(() => innerDelegate1.dispose()); + late final SimpleRouterDelegate innerDelegate2; + addTearDown(() => innerDelegate2.dispose()); + + // Creates the sub-router. + return Column( + children: [ + Text(Uri.decodeComponent(information!.uri.toString())), + Router( + backButtonDispatcher: innerDispatcher1, + routerDelegate: innerDelegate1 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? innerInformation) { + return Container(); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner1'), + ); + return SynchronousFuture(true); + }, + ), + ), + Router( + backButtonDispatcher: innerDispatcher2, + routerDelegate: innerDelegate2 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? innerInformation) { + return Container(); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner2'), + ); + return SynchronousFuture(true); + }, + ), + ), + ], + ); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped outer'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(outerDelegate.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - Text(Uri.decodeComponent(information!.uri.toString())), - Router( - backButtonDispatcher: innerDispatcher1, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? innerInformation) { - return Container(); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner1'), - ); - return SynchronousFuture(true); - }, - ), - ), - Router( - backButtonDispatcher: innerDispatcher2, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? innerInformation) { - return Container(); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner2'), - ); - return SynchronousFuture(true); - }, - ), - ), - ], - ); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped outer'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: outerDelegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -398,88 +433,107 @@ void main() { expect(find.text('popped inner2'), findsOneWidget); }); - testWidgets('ChildBackButtonDispatcher can be replaced without calling the takePriority', (WidgetTester tester) async { - + testWidgetsWithLeakTracking('ChildBackButtonDispatcher can be replaced without calling the takePriority', (WidgetTester tester) async { final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); + final SimpleRouterDelegate outerDelegate1 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + final SimpleRouterDelegate innerDelegate1 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? innerInformation) { + return Container(); + }, + ); + addTearDown(innerDelegate1.dispose); + + // Creates the sub-router. + return Column( + children: [ + const Text('initial'), + Router( + backButtonDispatcher: innerDispatcher, + routerDelegate: innerDelegate1, + ), + ], + ); + }, + ); + addTearDown(outerDelegate1.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - const Text('initial'), - Router( - backButtonDispatcher: innerDispatcher, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? innerInformation) { - return Container(); - }, - ), - ), - ], - ); - }, - ), + routerDelegate: outerDelegate1, ), )); // Creates a new child back button dispatcher and rebuild, this will cause // the old one to be replaced and discarded. innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); + final SimpleRouterDelegate outerDelegate2 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + final SimpleRouterDelegate innerDelegate2 = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? innerInformation) { + return Container(); + }, + ); + addTearDown(innerDelegate2.dispose); + + // Creates the sub-router. + return Column( + children: [ + const Text('initial'), + Router( + backButtonDispatcher: innerDispatcher, + routerDelegate: innerDelegate2, + ), + ], + ); + }, + ); + addTearDown(outerDelegate2.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - const Text('initial'), - Router( - backButtonDispatcher: innerDispatcher, - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? innerInformation) { - return Container(); - }, - ), - ), - ], - ); - }, - ), + routerDelegate: outerDelegate2, ), )); expect(tester.takeException(), isNull); }); -testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester tester) async { - +testWidgetsWithLeakTracking('ChildBackButtonDispatcher take priority recursively', (WidgetTester tester) async { final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); final BackButtonDispatcher innerDispatcher1 = ChildBackButtonDispatcher(outerDispatcher); final BackButtonDispatcher innerDispatcher2 = ChildBackButtonDispatcher(innerDispatcher1); final BackButtonDispatcher innerDispatcher3 = ChildBackButtonDispatcher(innerDispatcher2); + late final SimpleRouterDelegate outerDelegate; + addTearDown(() => outerDelegate.dispose()); + late final SimpleRouterDelegate innerDelegate1; + addTearDown(() => innerDelegate1.dispose()); + late final SimpleRouterDelegate innerDelegate2; + addTearDown(() => innerDelegate2.dispose()); + late final SimpleRouterDelegate innerDelegate3; + addTearDown(() => innerDelegate3.dispose()); bool isPopped = false; + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, - routerDelegate: SimpleRouterDelegate( + routerDelegate: outerDelegate = SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? information) { // Creates the sub-router. return Router( backButtonDispatcher: innerDispatcher1, - routerDelegate: SimpleRouterDelegate( + routerDelegate: innerDelegate1 = SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? innerInformation) { return Router( backButtonDispatcher: innerDispatcher2, - routerDelegate: SimpleRouterDelegate( + routerDelegate: innerDelegate2 = SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? innerInformation) { return Router( backButtonDispatcher: innerDispatcher3, - routerDelegate: SimpleRouterDelegate( + routerDelegate: innerDelegate3 = SimpleRouterDelegate( onPopRoute: () { isPopped = true; return SynchronousFuture(true); @@ -508,7 +562,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(isPopped, isTrue); }); - testWidgets('router does report URL change correctly', (WidgetTester tester) async { + testWidgetsWithLeakTracking('router does report URL change correctly', (WidgetTester tester) async { RouteInformation? reportedRouteInformation; RouteInformationReportingType? reportedType; final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( @@ -520,6 +574,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester reportedType = type; }, ); + addTearDown(provider.dispose); final SimpleRouterDelegate delegate = SimpleRouterDelegate( reportConfiguration: true, builder: (BuildContext context, RouteInformation? information) { @@ -532,6 +587,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ); return SynchronousFuture(true); }; + addTearDown(delegate.dispose); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); provider.value = RouteInformation( @@ -584,7 +640,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(reportedType, RouteInformationReportingType.none); }); - testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async { + testWidgetsWithLeakTracking('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async { RouteInformation? reportedRouteInformation; RouteInformationReportingType? reportedType; bool isNavigating = false; @@ -598,10 +654,12 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester reportedType = type; }, ); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); + addTearDown(delegate.dispose); delegate.builder = (BuildContext context, RouteInformation? information) { return ElevatedButton( child: Text(Uri.decodeComponent(information!.uri.toString())), @@ -662,7 +720,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester reportedRouteInformation = null; }); - testWidgets('router ignore navigating events updates RouteInformationProvider', (WidgetTester tester) async { + testWidgetsWithLeakTracking('router ignore navigating events updates RouteInformationProvider', (WidgetTester tester) async { RouteInformation? updatedRouteInformation; late RouteInformation nextRouteInformation; RouteInformationReportingType? reportingType; @@ -674,10 +732,12 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester reportingType = type; }, ); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); + addTearDown(delegate.dispose); delegate.builder = (BuildContext context, RouteInformation? information) { return ElevatedButton( child: Text(Uri.decodeComponent(information!.uri.toString())), @@ -717,7 +777,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(reportingType, RouteInformationReportingType.neglect); }); - testWidgets('state change without location changes updates RouteInformationProvider', (WidgetTester tester) async { + testWidgetsWithLeakTracking('state change without location changes updates RouteInformationProvider', (WidgetTester tester) async { RouteInformation? updatedRouteInformation; late RouteInformation nextRouteInformation; RouteInformationReportingType? reportingType; @@ -730,11 +790,13 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester reportingType = type; }, ); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), state: 'state1', ); final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); + addTearDown(delegate.dispose); delegate.builder = (BuildContext context, RouteInformation? information) { return ElevatedButton( child: Text(Uri.decodeComponent(information!.uri.toString())), @@ -770,12 +832,13 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(reportingType, RouteInformationReportingType.none); }); - testWidgets('PlatformRouteInformationProvider works', (WidgetTester tester) async { - final RouteInformationProvider provider = PlatformRouteInformationProvider( + testWidgetsWithLeakTracking('PlatformRouteInformationProvider works', (WidgetTester tester) async { + final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider( initialRouteInformation: RouteInformation( uri: Uri.parse('initial'), ), ); + addTearDown(provider.dispose); final SimpleRouterDelegate delegate = SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? information) { final List children = []; @@ -790,6 +853,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ); }, ); + addTearDown(delegate.dispose); await tester.pumpWidget(MaterialApp.router( routeInformationProvider: provider, @@ -821,7 +885,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('newTestRouteName'), findsOneWidget); }); - testWidgets('PlatformRouteInformationProvider updates route information', (WidgetTester tester) async { + testWidgetsWithLeakTracking('PlatformRouteInformationProvider updates route information', (WidgetTester tester) async { final List log = []; TestDefaultBinaryMessengerBinding .instance.defaultBinaryMessenger @@ -832,11 +896,12 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester return null; } ); - final RouteInformationProvider provider = PlatformRouteInformationProvider( + final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider( initialRouteInformation: RouteInformation( uri: Uri.parse('initial'), ), ); + addTearDown(provider.dispose); log.clear(); provider.routerReportsNewRouteInformation(RouteInformation(uri: Uri.parse('a'), state: true)); @@ -868,7 +933,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ]); }); - testWidgets('PlatformRouteInformationProvider does not push new entry if query parameters are semantically the same', (WidgetTester tester) async { + testWidgetsWithLeakTracking('PlatformRouteInformationProvider does not push new entry if query parameters are semantically the same', (WidgetTester tester) async { final List log = []; TestDefaultBinaryMessengerBinding .instance.defaultBinaryMessenger @@ -882,9 +947,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester final RouteInformation initial = RouteInformation( uri: Uri.parse('initial?a=ws/abcd'), ); - final RouteInformationProvider provider = PlatformRouteInformationProvider( + final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider( initialRouteInformation: initial ); + addTearDown(provider.dispose); // Make sure engine is updated with initial route provider.routerReportsNewRouteInformation(initial); log.clear(); @@ -938,19 +1004,21 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester log.clear(); }); - testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async { + testWidgetsWithLeakTracking('RootBackButtonDispatcher works', (WidgetTester tester) async { final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); - final RouteInformationProvider provider = PlatformRouteInformationProvider( + final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider( initialRouteInformation: RouteInformation( uri: Uri.parse('initial'), ), ); + addTearDown(provider.dispose); final SimpleRouterDelegate delegate = SimpleRouterDelegate( reportConfiguration: true, builder: (BuildContext context, RouteInformation? information) { return Text(Uri.decodeComponent(information!.uri.toString())); }, ); + addTearDown(delegate.dispose); delegate.onPopRoute = () { delegate.routeInformation = RouteInformation( uri: Uri.parse('popped'), @@ -973,42 +1041,46 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('popped'), findsOneWidget); }); - testWidgets('BackButtonListener takes priority over root back dispatcher', (WidgetTester tester) async { + testWidgetsWithLeakTracking('BackButtonListener takes priority over root back dispatcher', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + // Creates the sub-router. + return Column( + children: [ + Text(Uri.decodeComponent(information!.uri.toString())), + BackButtonListener( + child: Container(), + onBackButtonPressed: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner1'), + ); + return SynchronousFuture(true); + }, + ), + ], + ); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped outer'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - Text(Uri.decodeComponent(information!.uri.toString())), - BackButtonListener( - child: Container(), - onBackButtonPressed: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner1'), - ); - return SynchronousFuture(true); - }, - ), - ], - ); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped outer'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -1020,8 +1092,9 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('popped inner1'), findsOneWidget); }); - testWidgets('BackButtonListener updates callback if it has been changed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('BackButtonListener updates callback if it has been changed', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -1050,6 +1123,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ); return SynchronousFuture(true); }; + addTearDown(routerDelegate.dispose); await tester.pumpWidget(buildBoilerPlate( Router( @@ -1099,8 +1173,9 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('second callback'), findsOneWidget); }); - testWidgets('BackButtonListener clears callback if it is disposed', (WidgetTester tester) async { + testWidgetsWithLeakTracking('BackButtonListener clears callback if it is disposed', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -1129,6 +1204,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ); return SynchronousFuture(true); }; + addTearDown(routerDelegate.dispose); await tester.pumpWidget(buildBoilerPlate( Router( @@ -1169,50 +1245,54 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('popped outer'), findsOneWidget); }); - testWidgets('Nested backButtonListener should take priority', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Nested backButtonListener should take priority', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + // Creates the sub-router. + return Column( + children: [ + Text(Uri.decodeComponent(information!.uri.toString())), + BackButtonListener( + child: BackButtonListener( + child: Container(), + onBackButtonPressed: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner2'), + ); + return SynchronousFuture(true); + }, + ), + onBackButtonPressed: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner1'), + ); + return SynchronousFuture(true); + }, + ), + ], + ); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped outer'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - Text(Uri.decodeComponent(information!.uri.toString())), - BackButtonListener( - child: BackButtonListener( - child: Container(), - onBackButtonPressed: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner2'), - ); - return SynchronousFuture(true); - }, - ), - onBackButtonPressed: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner1'), - ); - return SynchronousFuture(true); - }, - ), - ], - ); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped outer'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -1224,50 +1304,54 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('popped inner2'), findsOneWidget); }); - testWidgets('Nested backButtonListener that returns false should call next on the line', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Nested backButtonListener that returns false should call next on the line', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + // Creates the sub-router. + return Column( + children: [ + Text(Uri.decodeComponent(information!.uri.toString())), + BackButtonListener( + child: BackButtonListener( + child: Container(), + onBackButtonPressed: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner2'), + ); + return SynchronousFuture(false); + }, + ), + onBackButtonPressed: () { + provider.value = RouteInformation( + uri: Uri.parse('popped inner1'), + ); + return SynchronousFuture(true); + }, + ), + ], + ); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped outer'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); + await tester.pumpWidget(buildBoilerPlate( Router( backButtonDispatcher: outerDispatcher, routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - // Creates the sub-router. - return Column( - children: [ - Text(Uri.decodeComponent(information!.uri.toString())), - BackButtonListener( - child: BackButtonListener( - child: Container(), - onBackButtonPressed: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner2'), - ); - return SynchronousFuture(false); - }, - ), - onBackButtonPressed: () { - provider.value = RouteInformation( - uri: Uri.parse('popped inner1'), - ); - return SynchronousFuture(true); - }, - ), - ], - ); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped outer'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, ), )); expect(find.text('initial'), findsOneWidget); @@ -1279,8 +1363,9 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('popped inner1'), findsOneWidget); }); - testWidgets('`didUpdateWidget` test', (WidgetTester tester) async { + testWidgetsWithLeakTracking('`didUpdateWidget` test', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); @@ -1316,6 +1401,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ); return SynchronousFuture(true); }; + addTearDown(routerDelegate.dispose); await tester.pumpWidget(buildBoilerPlate( Router( @@ -1337,11 +1423,17 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(find.text('second callback'), findsOneWidget); }); - testWidgets('Router reports location if it is different from location given by OS', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Router reports location if it is different from location given by OS', (WidgetTester tester) async { final List reportedRouteInformation = []; final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( onRouterReport: (RouteInformation info, RouteInformationReportingType type) => reportedRouteInformation.add(info), )..value = RouteInformation(uri: Uri.parse('/home')); + addTearDown(provider.dispose); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext _, RouteInformation? info) => Text('Current route: ${info?.uri}'), + reportConfiguration: true, + ); + addTearDown(delegate.dispose); await tester.pumpWidget(buildBoilerPlate( Router( @@ -1349,10 +1441,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester routeInformationParser: RedirectingInformationParser({ '/doesNotExist' : RouteInformation(uri: Uri.parse('/404')), }), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext _, RouteInformation? info) => Text('Current route: ${info?.uri}'), - reportConfiguration: true, - ), + routerDelegate: delegate, ), )); @@ -1366,12 +1455,25 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(reportedRouteInformation[1].uri.toString(), '/404'); }); - testWidgets('RouterInformationParser can look up dependencies and reparse', (WidgetTester tester) async { + testWidgetsWithLeakTracking('RouterInformationParser can look up dependencies and reparse', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); int expectedMaxLines = 1; bool parserCalled = false; final Widget router = Router( @@ -1381,17 +1483,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester final DefaultTextStyle style = DefaultTextStyle.of(context); return RouteInformation(uri: Uri.parse('${style.maxLines}')); }), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, backButtonDispatcher: dispatcher, ); await tester.pumpWidget(buildBoilerPlate( @@ -1419,12 +1511,25 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(parserCalled, isTrue); }); - testWidgets('RouterInformationParser can look up dependencies without reparsing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('RouterInformationParser can look up dependencies without reparsing', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + return Text(Uri.decodeComponent(information!.uri.toString())); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); const int expectedMaxLines = 1; bool parserCalled = false; final Widget router = Router( @@ -1434,17 +1539,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester final DefaultTextStyle style = context.getInheritedWidgetOfExactType()!; return RouteInformation(uri: Uri.parse('${style.maxLines}')); }), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - return Text(Uri.decodeComponent(information!.uri.toString())); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, backButtonDispatcher: dispatcher, ); await tester.pumpWidget(buildBoilerPlate( @@ -1474,12 +1569,26 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(parserCalled, isFalse); }); - testWidgets('Looks up dependencies in RouterDelegate does not trigger re-parsing', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Looks up dependencies in RouterDelegate does not trigger re-parsing', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); provider.value = RouteInformation( uri: Uri.parse('initial'), ); final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (BuildContext context, RouteInformation? information) { + final DefaultTextStyle style = DefaultTextStyle.of(context); + return Text('${style.maxLines}'); + }, + onPopRoute: () { + provider.value = RouteInformation( + uri: Uri.parse('popped'), + ); + return SynchronousFuture(true); + }, + ); + addTearDown(delegate.dispose); int expectedMaxLines = 1; bool parserCalled = false; final Widget router = Router( @@ -1488,18 +1597,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester parserCalled = true; return information; }), - routerDelegate: SimpleRouterDelegate( - builder: (BuildContext context, RouteInformation? information) { - final DefaultTextStyle style = DefaultTextStyle.of(context); - return Text('${style.maxLines}'); - }, - onPopRoute: () { - provider.value = RouteInformation( - uri: Uri.parse('popped'), - ); - return SynchronousFuture(true); - }, - ), + routerDelegate: delegate, backButtonDispatcher: dispatcher, ); await tester.pumpWidget(buildBoilerPlate( @@ -1528,14 +1626,19 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester expect(parserCalled, isFalse); }); - testWidgets('Router can initialize with RouterConfig', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Router can initialize with RouterConfig', (WidgetTester tester) async { const String expected = 'text'; + final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); + addTearDown(provider.dispose); + provider.value = RouteInformation(uri: Uri.parse('/')); + final SimpleRouterDelegate delegate = SimpleRouterDelegate( + builder: (_, __) => const Text(expected), + ); + addTearDown(delegate.dispose); final RouterConfig config = RouterConfig( - routeInformationProvider: SimpleRouteInformationProvider()..value = RouteInformation(uri: Uri.parse('/')), + routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), - routerDelegate: SimpleRouterDelegate( - builder: (_, __) => const Text(expected), - ), + routerDelegate: delegate, backButtonDispatcher: RootBackButtonDispatcher(), ); final Router router = Router.withConfig(config: config);