mirror of
https://github.com/flutter/packages.git
synced 2025-07-02 08:34:31 +08:00
[go_router] Adds parent navigator key to ShellRoute and StatefulShell… (#4201)
â¦Route. fixes https://github.com/flutter/flutter/issues/111678 fixes https://github.com/flutter/flutter/issues/128793
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
## 8.1.0
|
||||||
|
|
||||||
|
- Adds parent navigator key to ShellRoute and StatefulShellRoute.
|
||||||
|
|
||||||
## 8.0.5
|
## 8.0.5
|
||||||
|
|
||||||
- Fixes a bug that GoRouterState in top level redirect doesn't contain complete data.
|
- Fixes a bug that GoRouterState in top level redirect doesn't contain complete data.
|
||||||
|
@ -204,77 +204,73 @@ class RouteBuilder {
|
|||||||
keyToPages.putIfAbsent(navigatorKey, () => <Page<Object?>>[]).add(page);
|
keyToPages.putIfAbsent(navigatorKey, () => <Page<Object?>>[]).add(page);
|
||||||
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
||||||
routerNeglect, keyToPages, navigatorKey, registry);
|
routerNeglect, keyToPages, navigatorKey, registry);
|
||||||
} else if (route is GoRoute) {
|
} else {
|
||||||
page = _buildPageForGoRoute(context, state, match, route, pagePopContext);
|
// If this RouteBase is for a different Navigator, add it to the
|
||||||
// If this GoRoute is for a different Navigator, add it to the
|
|
||||||
// list of out of scope pages
|
// list of out of scope pages
|
||||||
final GlobalKey<NavigatorState> goRouteNavKey =
|
final GlobalKey<NavigatorState> routeNavKey =
|
||||||
route.parentNavigatorKey ?? navigatorKey;
|
route.parentNavigatorKey ?? navigatorKey;
|
||||||
|
if (route is GoRoute) {
|
||||||
|
page =
|
||||||
|
_buildPageForGoRoute(context, state, match, route, pagePopContext);
|
||||||
|
|
||||||
keyToPages.putIfAbsent(goRouteNavKey, () => <Page<Object?>>[]).add(page);
|
keyToPages.putIfAbsent(routeNavKey, () => <Page<Object?>>[]).add(page);
|
||||||
|
|
||||||
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
||||||
routerNeglect, keyToPages, navigatorKey, registry);
|
routerNeglect, keyToPages, navigatorKey, registry);
|
||||||
} else if (route is ShellRouteBase) {
|
} else if (route is ShellRouteBase) {
|
||||||
assert(startIndex + 1 < matchList.matches.length,
|
assert(startIndex + 1 < matchList.matches.length,
|
||||||
'Shell routes must always have child routes');
|
'Shell routes must always have child routes');
|
||||||
// The key for the Navigator that will display this ShellRoute's page.
|
|
||||||
final GlobalKey<NavigatorState> parentNavigatorKey = navigatorKey;
|
|
||||||
|
|
||||||
// Add an entry for the parent navigator if none exists.
|
// Add an entry for the parent navigator if none exists.
|
||||||
keyToPages.putIfAbsent(parentNavigatorKey, () => <Page<Object?>>[]);
|
//
|
||||||
|
// Calling _buildRecursive can result in adding pages to the
|
||||||
|
// parentNavigatorKey entry's list. Store the current length so
|
||||||
|
// that the page for this ShellRoute is placed at the right index.
|
||||||
|
final int shellPageIdx =
|
||||||
|
keyToPages.putIfAbsent(routeNavKey, () => <Page<Object?>>[]).length;
|
||||||
|
|
||||||
// Calling _buildRecursive can result in adding pages to the
|
// Find the the navigator key for the sub-route of this shell route.
|
||||||
// parentNavigatorKey entry's list. Store the current length so
|
final RouteBase subRoute = matchList.matches[startIndex + 1].route;
|
||||||
// that the page for this ShellRoute is placed at the right index.
|
final GlobalKey<NavigatorState> shellNavigatorKey =
|
||||||
final int shellPageIdx = keyToPages[parentNavigatorKey]!.length;
|
route.navigatorKeyForSubRoute(subRoute);
|
||||||
|
|
||||||
// Get the current sub-route of this shell route from the match list.
|
keyToPages.putIfAbsent(shellNavigatorKey, () => <Page<Object?>>[]);
|
||||||
final RouteBase subRoute = matchList.matches[startIndex + 1].route;
|
|
||||||
|
|
||||||
// The key to provide to the shell route's Navigator.
|
// Build the remaining pages
|
||||||
final GlobalKey<NavigatorState> shellNavigatorKey =
|
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
||||||
route.navigatorKeyForSubRoute(subRoute);
|
routerNeglect, keyToPages, shellNavigatorKey, registry);
|
||||||
|
|
||||||
// Add an entry for the shell route's navigator
|
final HeroController heroController = _goHeroCache.putIfAbsent(
|
||||||
keyToPages.putIfAbsent(shellNavigatorKey, () => <Page<Object?>>[]);
|
shellNavigatorKey, () => _getHeroController(context));
|
||||||
|
|
||||||
// Build the remaining pages
|
// Build the Navigator for this shell route
|
||||||
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
|
Widget buildShellNavigator(
|
||||||
routerNeglect, keyToPages, shellNavigatorKey, registry);
|
List<NavigatorObserver>? observers, String? restorationScopeId) {
|
||||||
|
return _buildNavigator(
|
||||||
|
pagePopContext.onPopPage,
|
||||||
|
keyToPages[shellNavigatorKey]!,
|
||||||
|
shellNavigatorKey,
|
||||||
|
observers: observers ?? const <NavigatorObserver>[],
|
||||||
|
restorationScopeId: restorationScopeId,
|
||||||
|
heroController: heroController,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final HeroController heroController = _goHeroCache.putIfAbsent(
|
// Call the ShellRouteBase to create/update the shell route state
|
||||||
shellNavigatorKey, () => _getHeroController(context));
|
final ShellRouteContext shellRouteContext = ShellRouteContext(
|
||||||
|
route: route,
|
||||||
// Build the Navigator for this shell route
|
routerState: state,
|
||||||
Widget buildShellNavigator(
|
navigatorKey: shellNavigatorKey,
|
||||||
List<NavigatorObserver>? observers, String? restorationScopeId) {
|
routeMatchList: matchList,
|
||||||
return _buildNavigator(
|
navigatorBuilder: buildShellNavigator,
|
||||||
pagePopContext.onPopPage,
|
|
||||||
keyToPages[shellNavigatorKey]!,
|
|
||||||
shellNavigatorKey,
|
|
||||||
observers: observers ?? const <NavigatorObserver>[],
|
|
||||||
restorationScopeId: restorationScopeId,
|
|
||||||
heroController: heroController,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Build the Page for this route
|
||||||
|
page = _buildPageForShellRoute(
|
||||||
|
context, state, match, route, pagePopContext, shellRouteContext);
|
||||||
|
// Place the ShellRoute's Page onto the list for the parent navigator.
|
||||||
|
keyToPages[routeNavKey]!.insert(shellPageIdx, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the ShellRouteBase to create/update the shell route state
|
|
||||||
final ShellRouteContext shellRouteContext = ShellRouteContext(
|
|
||||||
route: route,
|
|
||||||
routerState: state,
|
|
||||||
navigatorKey: shellNavigatorKey,
|
|
||||||
routeMatchList: matchList,
|
|
||||||
navigatorBuilder: buildShellNavigator,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the Page for this route
|
|
||||||
page = _buildPageForShellRoute(
|
|
||||||
context, state, match, route, pagePopContext, shellRouteContext);
|
|
||||||
// Place the ShellRoute's Page onto the list for the parent navigator.
|
|
||||||
keyToPages
|
|
||||||
.putIfAbsent(parentNavigatorKey, () => <Page<Object?>>[])
|
|
||||||
.insert(shellPageIdx, page);
|
|
||||||
}
|
}
|
||||||
if (page != null) {
|
if (page != null) {
|
||||||
registry[page] = state;
|
registry[page] = state;
|
||||||
|
@ -148,73 +148,61 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
|||||||
/// pageless route, such as a dialog or bottom sheet.
|
/// pageless route, such as a dialog or bottom sheet.
|
||||||
class _NavigatorStateIterator extends Iterator<NavigatorState> {
|
class _NavigatorStateIterator extends Iterator<NavigatorState> {
|
||||||
_NavigatorStateIterator(this.matchList, this.root)
|
_NavigatorStateIterator(this.matchList, this.root)
|
||||||
: index = matchList.matches.length;
|
: index = matchList.matches.length - 1;
|
||||||
|
|
||||||
final RouteMatchList matchList;
|
final RouteMatchList matchList;
|
||||||
int index = 0;
|
int index;
|
||||||
|
|
||||||
final NavigatorState root;
|
final NavigatorState root;
|
||||||
@override
|
@override
|
||||||
late NavigatorState current;
|
late NavigatorState current;
|
||||||
|
|
||||||
|
RouteBase _getRouteAtIndex(int index) => matchList.matches[index].route;
|
||||||
|
|
||||||
|
void _findsNextIndex() {
|
||||||
|
final GlobalKey<NavigatorState>? parentNavigatorKey =
|
||||||
|
_getRouteAtIndex(index).parentNavigatorKey;
|
||||||
|
if (parentNavigatorKey == null) {
|
||||||
|
index -= 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index -= 1; index >= 0; index -= 1) {
|
||||||
|
final RouteBase route = _getRouteAtIndex(index);
|
||||||
|
if (route is ShellRouteBase) {
|
||||||
|
if (route.navigatorKeyForSubRoute(_getRouteAtIndex(index + 1)) ==
|
||||||
|
parentNavigatorKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(root == parentNavigatorKey.currentState);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool moveNext() {
|
bool moveNext() {
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
late RouteBase subRoute;
|
_findsNextIndex();
|
||||||
for (index -= 1; index >= 0; index -= 1) {
|
|
||||||
final RouteMatch match = matchList.matches[index];
|
|
||||||
final RouteBase route = match.route;
|
|
||||||
if (route is GoRoute && route.parentNavigatorKey != null) {
|
|
||||||
final GlobalKey<NavigatorState> parentNavigatorKey =
|
|
||||||
route.parentNavigatorKey!;
|
|
||||||
final ModalRoute<Object?>? parentModalRoute =
|
|
||||||
ModalRoute.of(parentNavigatorKey.currentContext!);
|
|
||||||
// The ModalRoute can be null if the parentNavigatorKey references the
|
|
||||||
// root navigator.
|
|
||||||
if (parentModalRoute == null) {
|
|
||||||
index = -1;
|
|
||||||
assert(root == parentNavigatorKey.currentState);
|
|
||||||
current = root;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// It must be a ShellRoute that holds this parentNavigatorKey;
|
|
||||||
// otherwise, parentModalRoute would have been null. Updates the index
|
|
||||||
// to the ShellRoute
|
|
||||||
for (index -= 1; index >= 0; index -= 1) {
|
|
||||||
final RouteBase route = matchList.matches[index].route;
|
|
||||||
if (route is ShellRoute) {
|
|
||||||
if (route.navigatorKey == parentNavigatorKey) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// There may be a pageless route on top of ModalRoute that the
|
|
||||||
// NavigatorState of parentNavigatorKey is in. For example, an open
|
|
||||||
// dialog. In that case we want to find the navigator that host the
|
|
||||||
// pageless route.
|
|
||||||
if (parentModalRoute.isCurrent == false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = parentNavigatorKey.currentState!;
|
while (index >= 0) {
|
||||||
return true;
|
final RouteBase route = _getRouteAtIndex(index);
|
||||||
} else if (route is ShellRouteBase) {
|
if (route is ShellRouteBase) {
|
||||||
|
final GlobalKey<NavigatorState> navigatorKey =
|
||||||
|
route.navigatorKeyForSubRoute(_getRouteAtIndex(index + 1));
|
||||||
// Must have a ModalRoute parent because the navigator ShellRoute
|
// Must have a ModalRoute parent because the navigator ShellRoute
|
||||||
// created must not be the root navigator.
|
// created must not be the root navigator.
|
||||||
final GlobalKey<NavigatorState> navigatorKey =
|
|
||||||
route.navigatorKeyForSubRoute(subRoute);
|
|
||||||
final ModalRoute<Object?> parentModalRoute =
|
final ModalRoute<Object?> parentModalRoute =
|
||||||
ModalRoute.of(navigatorKey.currentContext!)!;
|
ModalRoute.of(navigatorKey.currentContext!)!;
|
||||||
// There may be pageless route on top of ModalRoute that the
|
// There may be pageless route on top of ModalRoute that the
|
||||||
// parentNavigatorKey is in. For example an open dialog.
|
// parentNavigatorKey is in. For example an open dialog.
|
||||||
if (parentModalRoute.isCurrent == false) {
|
if (parentModalRoute.isCurrent) {
|
||||||
continue;
|
current = navigatorKey.currentState!;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
current = navigatorKey.currentState!;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
subRoute = route;
|
_findsNextIndex();
|
||||||
}
|
}
|
||||||
assert(index == -1);
|
assert(index == -1);
|
||||||
current = root;
|
current = root;
|
||||||
|
@ -98,12 +98,20 @@ import 'typedefs.dart';
|
|||||||
@immutable
|
@immutable
|
||||||
abstract class RouteBase {
|
abstract class RouteBase {
|
||||||
const RouteBase._({
|
const RouteBase._({
|
||||||
this.routes = const <RouteBase>[],
|
required this.routes,
|
||||||
|
required this.parentNavigatorKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The list of child routes associated with this route.
|
/// The list of child routes associated with this route.
|
||||||
final List<RouteBase> routes;
|
final List<RouteBase> routes;
|
||||||
|
|
||||||
|
/// An optional key specifying which Navigator to display this route's screen
|
||||||
|
/// onto.
|
||||||
|
///
|
||||||
|
/// Specifying the root Navigator will stack this route onto that
|
||||||
|
/// Navigator instead of the nearest ShellRoute ancestor.
|
||||||
|
final GlobalKey<NavigatorState>? parentNavigatorKey;
|
||||||
|
|
||||||
/// Builds a lists containing the provided routes along with all their
|
/// Builds a lists containing the provided routes along with all their
|
||||||
/// descendant [routes].
|
/// descendant [routes].
|
||||||
static Iterable<RouteBase> routesRecursively(Iterable<RouteBase> routes) {
|
static Iterable<RouteBase> routesRecursively(Iterable<RouteBase> routes) {
|
||||||
@ -137,7 +145,7 @@ class GoRoute extends RouteBase {
|
|||||||
this.name,
|
this.name,
|
||||||
this.builder,
|
this.builder,
|
||||||
this.pageBuilder,
|
this.pageBuilder,
|
||||||
this.parentNavigatorKey,
|
super.parentNavigatorKey,
|
||||||
this.redirect,
|
this.redirect,
|
||||||
super.routes = const <RouteBase>[],
|
super.routes = const <RouteBase>[],
|
||||||
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
|
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
|
||||||
@ -301,13 +309,6 @@ class GoRoute extends RouteBase {
|
|||||||
/// re-evaluation will be triggered if the [InheritedWidget] changes.
|
/// re-evaluation will be triggered if the [InheritedWidget] changes.
|
||||||
final GoRouterRedirect? redirect;
|
final GoRouterRedirect? redirect;
|
||||||
|
|
||||||
/// An optional key specifying which Navigator to display this route's screen
|
|
||||||
/// onto.
|
|
||||||
///
|
|
||||||
/// Specifying the root Navigator will stack this route onto that
|
|
||||||
/// Navigator instead of the nearest ShellRoute ancestor.
|
|
||||||
final GlobalKey<NavigatorState>? parentNavigatorKey;
|
|
||||||
|
|
||||||
// TODO(chunhtai): move all regex related help methods to path_utils.dart.
|
// TODO(chunhtai): move all regex related help methods to path_utils.dart.
|
||||||
/// Match this route against a location.
|
/// Match this route against a location.
|
||||||
RegExpMatch? matchPatternAsPrefix(String loc) =>
|
RegExpMatch? matchPatternAsPrefix(String loc) =>
|
||||||
@ -333,7 +334,9 @@ class GoRoute extends RouteBase {
|
|||||||
/// as [ShellRoute] and [StatefulShellRoute].
|
/// as [ShellRoute] and [StatefulShellRoute].
|
||||||
abstract class ShellRouteBase extends RouteBase {
|
abstract class ShellRouteBase extends RouteBase {
|
||||||
/// Constructs a [ShellRouteBase].
|
/// Constructs a [ShellRouteBase].
|
||||||
const ShellRouteBase._({super.routes}) : super._();
|
const ShellRouteBase._(
|
||||||
|
{required super.routes, required super.parentNavigatorKey})
|
||||||
|
: super._();
|
||||||
|
|
||||||
/// Attempts to build the Widget representing this shell route.
|
/// Attempts to build the Widget representing this shell route.
|
||||||
///
|
///
|
||||||
@ -496,7 +499,8 @@ class ShellRoute extends ShellRouteBase {
|
|||||||
this.builder,
|
this.builder,
|
||||||
this.pageBuilder,
|
this.pageBuilder,
|
||||||
this.observers,
|
this.observers,
|
||||||
super.routes,
|
required super.routes,
|
||||||
|
super.parentNavigatorKey,
|
||||||
GlobalKey<NavigatorState>? navigatorKey,
|
GlobalKey<NavigatorState>? navigatorKey,
|
||||||
this.restorationScopeId,
|
this.restorationScopeId,
|
||||||
}) : assert(routes.isNotEmpty),
|
}) : assert(routes.isNotEmpty),
|
||||||
@ -653,6 +657,7 @@ class StatefulShellRoute extends ShellRouteBase {
|
|||||||
this.builder,
|
this.builder,
|
||||||
this.pageBuilder,
|
this.pageBuilder,
|
||||||
required this.navigatorContainerBuilder,
|
required this.navigatorContainerBuilder,
|
||||||
|
super.parentNavigatorKey,
|
||||||
this.restorationScopeId,
|
this.restorationScopeId,
|
||||||
}) : assert(branches.isNotEmpty),
|
}) : assert(branches.isNotEmpty),
|
||||||
assert((pageBuilder != null) ^ (builder != null),
|
assert((pageBuilder != null) ^ (builder != null),
|
||||||
@ -676,12 +681,14 @@ class StatefulShellRoute extends ShellRouteBase {
|
|||||||
StatefulShellRoute.indexedStack({
|
StatefulShellRoute.indexedStack({
|
||||||
required List<StatefulShellBranch> branches,
|
required List<StatefulShellBranch> branches,
|
||||||
StatefulShellRouteBuilder? builder,
|
StatefulShellRouteBuilder? builder,
|
||||||
|
GlobalKey<NavigatorState>? parentNavigatorKey,
|
||||||
StatefulShellRoutePageBuilder? pageBuilder,
|
StatefulShellRoutePageBuilder? pageBuilder,
|
||||||
String? restorationScopeId,
|
String? restorationScopeId,
|
||||||
}) : this(
|
}) : this(
|
||||||
branches: branches,
|
branches: branches,
|
||||||
builder: builder,
|
builder: builder,
|
||||||
pageBuilder: pageBuilder,
|
pageBuilder: pageBuilder,
|
||||||
|
parentNavigatorKey: parentNavigatorKey,
|
||||||
restorationScopeId: restorationScopeId,
|
restorationScopeId: restorationScopeId,
|
||||||
navigatorContainerBuilder: _indexedStackContainerBuilder,
|
navigatorContainerBuilder: _indexedStackContainerBuilder,
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: go_router
|
name: go_router
|
||||||
description: A declarative router for Flutter based on Navigation 2 supporting
|
description: A declarative router for Flutter based on Navigation 2 supporting
|
||||||
deep linking, data-driven routes and more
|
deep linking, data-driven routes and more
|
||||||
version: 8.0.5
|
version: 8.1.0
|
||||||
repository: https://github.com/flutter/packages/tree/main/packages/go_router
|
repository: https://github.com/flutter/packages/tree/main/packages/go_router
|
||||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
|
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:go_router/src/match.dart';
|
import 'package:go_router/src/match.dart';
|
||||||
import 'package:go_router/src/misc/error_screen.dart';
|
import 'package:go_router/src/misc/error_screen.dart';
|
||||||
|
import 'package:go_router/src/misc/errors.dart';
|
||||||
|
|
||||||
import 'test_helpers.dart';
|
import 'test_helpers.dart';
|
||||||
|
|
||||||
@ -96,6 +97,73 @@ void main() {
|
|||||||
await goRouter.routerDelegate.popRoute();
|
await goRouter.routerDelegate.popRoute();
|
||||||
expect(await goRouter.routerDelegate.popRoute(), isFalse);
|
expect(await goRouter.routerDelegate.popRoute(), isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('throw if nothing to pop', (WidgetTester tester) async {
|
||||||
|
final GlobalKey<NavigatorState> rootKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
||||||
|
final GoRouter goRouter = await createRouter(
|
||||||
|
<RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: rootKey,
|
||||||
|
builder: (_, __, Widget child) => child,
|
||||||
|
routes: <RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
parentNavigatorKey: rootKey,
|
||||||
|
navigatorKey: navKey,
|
||||||
|
builder: (_, __, Widget child) => child,
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
parentNavigatorKey: navKey,
|
||||||
|
builder: (_, __) => const Text('Home'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tester,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('Home'), findsOneWidget);
|
||||||
|
String? message;
|
||||||
|
try {
|
||||||
|
goRouter.pop();
|
||||||
|
} on GoError catch (e) {
|
||||||
|
message = e.message;
|
||||||
|
}
|
||||||
|
expect(message, 'There is nothing to pop');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('poproute return false if nothing to pop',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final GlobalKey<NavigatorState> rootKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
||||||
|
final GoRouter goRouter = await createRouter(
|
||||||
|
<RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: rootKey,
|
||||||
|
builder: (_, __, Widget child) => child,
|
||||||
|
routes: <RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
parentNavigatorKey: rootKey,
|
||||||
|
navigatorKey: navKey,
|
||||||
|
builder: (_, __, Widget child) => child,
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
parentNavigatorKey: navKey,
|
||||||
|
builder: (_, __) => const Text('Home'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tester,
|
||||||
|
);
|
||||||
|
expect(await goRouter.routerDelegate.popRoute(), isFalse);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('push', () {
|
group('push', () {
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import 'test_helpers.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('throws when a builder is not set', () {
|
test('throws when a builder is not set', () {
|
||||||
expect(() => GoRoute(path: '/'), throwsA(isAssertionError));
|
expect(() => GoRoute(path: '/'), throwsA(isAssertionError));
|
||||||
@ -17,4 +20,139 @@ void main() {
|
|||||||
test('does not throw when only redirect is provided', () {
|
test('does not throw when only redirect is provided', () {
|
||||||
GoRoute(path: '/', redirect: (_, __) => '/a');
|
GoRoute(path: '/', redirect: (_, __) => '/a');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ShellRoute can use parent navigator key',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final GlobalKey<NavigatorState> rootNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> shellNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
final List<RouteBase> routes = <RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: shellNavigatorKey,
|
||||||
|
builder: (BuildContext context, GoRouterState state, Widget child) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Screen A'),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/b',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Text('Screen B'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
parentNavigatorKey: rootNavigatorKey,
|
||||||
|
builder:
|
||||||
|
(BuildContext context, GoRouterState state, Widget child) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Screen D'),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: 'c',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Text('Screen C'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await createRouter(routes, tester,
|
||||||
|
initialLocation: '/b/c', navigatorKey: rootNavigatorKey);
|
||||||
|
expect(find.text('Screen A'), findsNothing);
|
||||||
|
expect(find.text('Screen B'), findsNothing);
|
||||||
|
expect(find.text('Screen D'), findsOneWidget);
|
||||||
|
expect(find.text('Screen C'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('StatefulShellRoute can use parent navigator key',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final GlobalKey<NavigatorState> rootNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> shellNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
final List<RouteBase> routes = <RouteBase>[
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: shellNavigatorKey,
|
||||||
|
builder: (BuildContext context, GoRouterState state, Widget child) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Screen A'),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/b',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Text('Screen B'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
routes: <RouteBase>[
|
||||||
|
StatefulShellRoute.indexedStack(
|
||||||
|
parentNavigatorKey: rootNavigatorKey,
|
||||||
|
builder: (_, __, StatefulNavigationShell navigationShell) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Screen D'),
|
||||||
|
Expanded(child: navigationShell),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
branches: <StatefulShellBranch>[
|
||||||
|
StatefulShellBranch(
|
||||||
|
routes: <RouteBase>[
|
||||||
|
GoRoute(
|
||||||
|
path: 'c',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Text('Screen C'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await createRouter(routes, tester,
|
||||||
|
initialLocation: '/b/c', navigatorKey: rootNavigatorKey);
|
||||||
|
expect(find.text('Screen A'), findsNothing);
|
||||||
|
expect(find.text('Screen B'), findsNothing);
|
||||||
|
expect(find.text('Screen D'), findsOneWidget);
|
||||||
|
expect(find.text('Screen C'), findsOneWidget);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user