From 08e61631ff75ebce7815439dd2c60be5a3907615 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 12 Sep 2022 14:57:55 -0700 Subject: [PATCH] Allow Navigator to inherit traversal policy from parent. (#110818) --- .../flutter/lib/src/widgets/navigator.dart | 1 + .../flutter/test/widgets/navigator_test.dart | 158 +++++++++++------- 2 files changed, 101 insertions(+), 58 deletions(-) diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 792af72fe6..4312ddb71a 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -5235,6 +5235,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res child: AbsorbPointer( absorbing: false, // it's mutated directly by _cancelActivePointers above child: FocusTraversalGroup( + policy: FocusTraversalGroup.maybeOf(context), child: Focus( focusNode: focusNode, autofocus: true, diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index ba157a91ae..e0f538e399 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -3775,72 +3775,71 @@ void main() { expect(focusNode.hasFocus, true); }); - testWidgets('Navigator does not request focus if requestFocus is false', - (WidgetTester tester) async { - final GlobalKey navigatorKey = GlobalKey(); - final GlobalKey innerKey = GlobalKey(); - final Map routes = { - '/': const Text('A'), - '/second': Text('B', key: innerKey), - }; - late final NavigatorState navigator = - navigatorKey.currentState! as NavigatorState; - final FocusScopeNode focusNode = FocusScopeNode(); + testWidgets('Navigator does not request focus if requestFocus is false', (WidgetTester tester) async { + final GlobalKey navigatorKey = GlobalKey(); + final GlobalKey innerKey = GlobalKey(); + final Map routes = { + '/': const Text('A'), + '/second': Text('B', key: innerKey), + }; + late final NavigatorState navigator = + navigatorKey.currentState! as NavigatorState; + final FocusScopeNode focusNode = FocusScopeNode(); - await tester.pumpWidget(Column( - children: [ - FocusScope(node: focusNode, child: Container()), - Expanded( - child: MaterialApp( - home: Navigator( - key: navigatorKey, - onGenerateRoute: (RouteSettings settings) { - return PageRouteBuilder( - settings: settings, - pageBuilder: (BuildContext _, Animation __, - Animation ___) { - return routes[settings.name!]!; - }, - ); + await tester.pumpWidget(Column( + children: [ + FocusScope(node: focusNode, child: Container()), + Expanded( + child: MaterialApp( + home: Navigator( + key: navigatorKey, + onGenerateRoute: (RouteSettings settings) { + return PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext _, Animation __, + Animation ___) { + return routes[settings.name!]!; }, - requestFocus: false, - ), - ), + ); + }, + requestFocus: false, ), - ], - )); - expect(find.text('A'), findsOneWidget); - expect(find.text('B', skipOffstage: false), findsNothing); - expect(focusNode.hasFocus, false); + ), + ), + ], + )); + expect(find.text('A'), findsOneWidget); + expect(find.text('B', skipOffstage: false), findsNothing); + expect(focusNode.hasFocus, false); - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(focusNode.hasFocus, true); + focusNode.requestFocus(); + await tester.pumpAndSettle(); + expect(focusNode.hasFocus, true); - navigator.pushNamed('/second'); - await tester.pumpAndSettle(); - expect(find.text('A', skipOffstage: false), findsOneWidget); - expect(find.text('B'), findsOneWidget); - expect(focusNode.hasFocus, true); + navigator.pushNamed('/second'); + await tester.pumpAndSettle(); + expect(find.text('A', skipOffstage: false), findsOneWidget); + expect(find.text('B'), findsOneWidget); + expect(focusNode.hasFocus, true); - navigator.pop(); - await tester.pumpAndSettle(); - expect(find.text('A'), findsOneWidget); - expect(find.text('B', skipOffstage: false), findsNothing); - expect(focusNode.hasFocus, true); + navigator.pop(); + await tester.pumpAndSettle(); + expect(find.text('A'), findsOneWidget); + expect(find.text('B', skipOffstage: false), findsNothing); + expect(focusNode.hasFocus, true); - navigator.pushReplacementNamed('/second'); - await tester.pumpAndSettle(); - expect(find.text('A', skipOffstage: false), findsNothing); - expect(find.text('B'), findsOneWidget); - expect(focusNode.hasFocus, true); + navigator.pushReplacementNamed('/second'); + await tester.pumpAndSettle(); + expect(find.text('A', skipOffstage: false), findsNothing); + expect(find.text('B'), findsOneWidget); + expect(focusNode.hasFocus, true); - ModalRoute.of(innerKey.currentContext!)!.addLocalHistoryEntry( - LocalHistoryEntry(), - ); - await tester.pumpAndSettle(); - expect(focusNode.hasFocus, true); - }); + ModalRoute.of(innerKey.currentContext!)!.addLocalHistoryEntry( + LocalHistoryEntry(), + ); + await tester.pumpAndSettle(); + expect(focusNode.hasFocus, true); + }); testWidgets('class implementing NavigatorObserver can be used without problems', (WidgetTester tester) async { final _MockNavigatorObserver observer = _MockNavigatorObserver(); @@ -3873,6 +3872,49 @@ void main() { await tester.pumpWidget(Container(child: build(key))); observer._checkInvocations([#navigator, #navigator]); }); + + testWidgets("Navigator doesn't override FocusTraversalPolicy of ancestors", (WidgetTester tester) async { + FocusTraversalPolicy? policy; + await tester.pumpWidget( + TestDependencies( + child: FocusTraversalGroup( + policy: WidgetOrderTraversalPolicy(), + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation __, Animation ___) { + policy = FocusTraversalGroup.of(context); + return const SizedBox(); + }, + ); + }, + ), + ), + ), + ); + expect(policy, isA()); + }); + + testWidgets('Navigator inserts ReadingOrderTraversalPolicy if no ancestor has a policy', (WidgetTester tester) async { + FocusTraversalPolicy? policy; + await tester.pumpWidget( + TestDependencies( + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return PageRouteBuilder( + settings: settings, + pageBuilder: (BuildContext context, Animation __, Animation ___) { + policy = FocusTraversalGroup.of(context); + return const SizedBox(); + }, + ); + }, + ), + ), + ); + expect(policy, isA()); + }); } typedef AnnouncementCallBack = void Function(Route?);