mirror of
https://github.com/flutter/packages.git
synced 2025-07-04 09:38:17 +08:00
[go_router]Fixes GoRouterState.location and GoRouterState.param to return correct value (#2786)
* Fixes GoRouterState.location and GoRouterState.param to return correct value * update * format
This commit is contained in:
@ -1,3 +1,8 @@
|
|||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
- Fixes `GoRouterState.location` and `GoRouterState.param` to return correct value.
|
||||||
|
- Cleans up `RouteMatch` and `RouteMatchList` API.
|
||||||
|
|
||||||
## 5.1.10
|
## 5.1.10
|
||||||
|
|
||||||
- Fixes link of ShellRoute in README.
|
- Fixes link of ShellRoute in README.
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'configuration.dart';
|
import 'configuration.dart';
|
||||||
|
import 'delegate.dart';
|
||||||
import 'logging.dart';
|
import 'logging.dart';
|
||||||
import 'match.dart';
|
import 'match.dart';
|
||||||
import 'matching.dart';
|
import 'matching.dart';
|
||||||
@ -75,11 +76,7 @@ class RouteBuilder {
|
|||||||
registry: _registry, child: result);
|
registry: _registry, child: result);
|
||||||
} on _RouteBuilderError catch (e) {
|
} on _RouteBuilderError catch (e) {
|
||||||
return _buildErrorNavigator(
|
return _buildErrorNavigator(
|
||||||
context,
|
context, e, matchList.uri, pop, configuration.navigatorKey);
|
||||||
e,
|
|
||||||
Uri.parse(matchList.location.toString()),
|
|
||||||
pop,
|
|
||||||
configuration.navigatorKey);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -124,13 +121,12 @@ class RouteBuilder {
|
|||||||
try {
|
try {
|
||||||
final Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPage =
|
final Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPage =
|
||||||
<GlobalKey<NavigatorState>, List<Page<Object?>>>{};
|
<GlobalKey<NavigatorState>, List<Page<Object?>>>{};
|
||||||
final Map<String, String> params = <String, String>{};
|
|
||||||
_buildRecursive(context, matchList, 0, onPop, routerNeglect, keyToPage,
|
_buildRecursive(context, matchList, 0, onPop, routerNeglect, keyToPage,
|
||||||
params, navigatorKey, registry);
|
navigatorKey, registry);
|
||||||
return keyToPage[navigatorKey]!;
|
return keyToPage[navigatorKey]!;
|
||||||
} on _RouteBuilderError catch (e) {
|
} on _RouteBuilderError catch (e) {
|
||||||
return <Page<Object?>>[
|
return <Page<Object?>>[
|
||||||
_buildErrorPage(context, e, matchList.location),
|
_buildErrorPage(context, e, matchList.uri),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,7 +138,6 @@ class RouteBuilder {
|
|||||||
VoidCallback pop,
|
VoidCallback pop,
|
||||||
bool routerNeglect,
|
bool routerNeglect,
|
||||||
Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPages,
|
Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPages,
|
||||||
Map<String, String> params,
|
|
||||||
GlobalKey<NavigatorState> navigatorKey,
|
GlobalKey<NavigatorState> navigatorKey,
|
||||||
Map<Page<Object?>, GoRouterState> registry,
|
Map<Page<Object?>, GoRouterState> registry,
|
||||||
) {
|
) {
|
||||||
@ -157,11 +152,7 @@ class RouteBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final RouteBase route = match.route;
|
final RouteBase route = match.route;
|
||||||
final Map<String, String> newParams = <String, String>{
|
final GoRouterState state = buildState(matchList, match);
|
||||||
...params,
|
|
||||||
...match.decodedParams
|
|
||||||
};
|
|
||||||
final GoRouterState state = buildState(match, newParams);
|
|
||||||
if (route is GoRoute) {
|
if (route is GoRoute) {
|
||||||
final Page<Object?> page = _buildPageForRoute(context, state, match);
|
final Page<Object?> page = _buildPageForRoute(context, state, match);
|
||||||
registry[page] = state;
|
registry[page] = state;
|
||||||
@ -173,7 +164,7 @@ class RouteBuilder {
|
|||||||
keyToPages.putIfAbsent(goRouteNavKey, () => <Page<Object?>>[]).add(page);
|
keyToPages.putIfAbsent(goRouteNavKey, () => <Page<Object?>>[]).add(page);
|
||||||
|
|
||||||
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
||||||
keyToPages, newParams, navigatorKey, registry);
|
keyToPages, navigatorKey, registry);
|
||||||
} else if (route is ShellRoute) {
|
} else if (route is ShellRoute) {
|
||||||
// The key for the Navigator that will display this ShellRoute's page.
|
// The key for the Navigator that will display this ShellRoute's page.
|
||||||
final GlobalKey<NavigatorState> parentNavigatorKey = navigatorKey;
|
final GlobalKey<NavigatorState> parentNavigatorKey = navigatorKey;
|
||||||
@ -194,7 +185,7 @@ class RouteBuilder {
|
|||||||
|
|
||||||
// Build the remaining pages
|
// Build the remaining pages
|
||||||
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
||||||
keyToPages, newParams, shellNavigatorKey, registry);
|
keyToPages, shellNavigatorKey, registry);
|
||||||
|
|
||||||
// Build the Navigator
|
// Build the Navigator
|
||||||
final Widget child = _buildNavigator(
|
final Widget child = _buildNavigator(
|
||||||
@ -235,25 +226,27 @@ class RouteBuilder {
|
|||||||
/// Helper method that builds a [GoRouterState] object for the given [match]
|
/// Helper method that builds a [GoRouterState] object for the given [match]
|
||||||
/// and [params].
|
/// and [params].
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
GoRouterState buildState(RouteMatch match, Map<String, String> params) {
|
GoRouterState buildState(RouteMatchList matchList, RouteMatch match) {
|
||||||
final RouteBase route = match.route;
|
final RouteBase route = match.route;
|
||||||
String? name = '';
|
String? name;
|
||||||
String path = '';
|
String path = '';
|
||||||
if (route is GoRoute) {
|
if (route is GoRoute) {
|
||||||
name = route.name;
|
name = route.name;
|
||||||
path = route.path;
|
path = route.path;
|
||||||
}
|
}
|
||||||
|
final RouteMatchList effectiveMatchList =
|
||||||
|
match is ImperativeRouteMatch ? match.matches : matchList;
|
||||||
return GoRouterState(
|
return GoRouterState(
|
||||||
configuration,
|
configuration,
|
||||||
location: match.fullUriString,
|
location: effectiveMatchList.uri.toString(),
|
||||||
subloc: match.subloc,
|
subloc: match.subloc,
|
||||||
name: name,
|
name: name,
|
||||||
path: path,
|
path: path,
|
||||||
fullpath: match.fullpath,
|
fullpath: effectiveMatchList.fullpath,
|
||||||
params: params,
|
params: effectiveMatchList.pathParameters,
|
||||||
error: match.error,
|
error: match.error,
|
||||||
queryParams: match.queryParams,
|
queryParams: effectiveMatchList.uri.queryParameters,
|
||||||
queryParametersAll: match.queryParametersAll,
|
queryParametersAll: effectiveMatchList.uri.queryParametersAll,
|
||||||
extra: match.extra,
|
extra: match.extra,
|
||||||
pageKey: match.pageKey,
|
pageKey: match.pageKey,
|
||||||
);
|
);
|
||||||
@ -425,6 +418,7 @@ class RouteBuilder {
|
|||||||
queryParams: uri.queryParameters,
|
queryParams: uri.queryParameters,
|
||||||
queryParametersAll: uri.queryParametersAll,
|
queryParametersAll: uri.queryParametersAll,
|
||||||
error: Exception(error),
|
error: Exception(error),
|
||||||
|
pageKey: const ValueKey<String>('error'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the error page builder is provided, use that, otherwise, if the error
|
// If the error page builder is provided, use that, otherwise, if the error
|
||||||
|
@ -6,9 +6,9 @@ import 'package:flutter/widgets.dart';
|
|||||||
|
|
||||||
import 'configuration.dart';
|
import 'configuration.dart';
|
||||||
import 'logging.dart';
|
import 'logging.dart';
|
||||||
|
import 'misc/errors.dart';
|
||||||
import 'path_utils.dart';
|
import 'path_utils.dart';
|
||||||
import 'typedefs.dart';
|
import 'typedefs.dart';
|
||||||
|
|
||||||
export 'route.dart';
|
export 'route.dart';
|
||||||
export 'state.dart';
|
export 'state.dart';
|
||||||
|
|
||||||
@ -20,34 +20,42 @@ class RouteConfiguration {
|
|||||||
required this.redirectLimit,
|
required this.redirectLimit,
|
||||||
required this.topRedirect,
|
required this.topRedirect,
|
||||||
required this.navigatorKey,
|
required this.navigatorKey,
|
||||||
}) {
|
}) : assert(_debugCheckPath(routes, true)),
|
||||||
|
assert(
|
||||||
|
_debugVerifyNoDuplicatePathParameter(routes, <String, GoRoute>{})),
|
||||||
|
assert(_debugCheckParentNavigatorKeys(
|
||||||
|
routes, <GlobalKey<NavigatorState>>[navigatorKey])) {
|
||||||
_cacheNameToPath('', routes);
|
_cacheNameToPath('', routes);
|
||||||
|
|
||||||
log.info(_debugKnownRoutes());
|
log.info(_debugKnownRoutes());
|
||||||
|
}
|
||||||
|
|
||||||
assert(() {
|
static bool _debugCheckPath(List<RouteBase> routes, bool isTopLevel) {
|
||||||
for (final RouteBase route in routes) {
|
|
||||||
if (route is GoRoute && !route.path.startsWith('/')) {
|
|
||||||
assert(route.path.startsWith('/'),
|
|
||||||
'top-level path must start with "/": ${route.path}');
|
|
||||||
} else if (route is ShellRoute) {
|
|
||||||
for (final RouteBase route in routes) {
|
for (final RouteBase route in routes) {
|
||||||
|
late bool subRouteIsTopLevel;
|
||||||
if (route is GoRoute) {
|
if (route is GoRoute) {
|
||||||
|
if (isTopLevel) {
|
||||||
assert(route.path.startsWith('/'),
|
assert(route.path.startsWith('/'),
|
||||||
'top-level path must start with "/": ${route.path}');
|
'top-level path must start with "/": $route');
|
||||||
|
} else {
|
||||||
|
assert(!route.path.startsWith('/') && !route.path.endsWith('/'),
|
||||||
|
'sub-route path may not start or end with /: $route');
|
||||||
}
|
}
|
||||||
|
subRouteIsTopLevel = false;
|
||||||
|
} else if (route is ShellRoute) {
|
||||||
|
subRouteIsTopLevel = isTopLevel;
|
||||||
}
|
}
|
||||||
|
_debugCheckPath(route.routes, subRouteIsTopLevel);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that each parentNavigatorKey refers to either a ShellRoute's
|
// Check that each parentNavigatorKey refers to either a ShellRoute's
|
||||||
// navigatorKey or the root navigator key.
|
// navigatorKey or the root navigator key.
|
||||||
void checkParentNavigatorKeys(
|
static bool _debugCheckParentNavigatorKeys(
|
||||||
List<RouteBase> routes, List<GlobalKey<NavigatorState>> allowedKeys) {
|
List<RouteBase> routes, List<GlobalKey<NavigatorState>> allowedKeys) {
|
||||||
for (final RouteBase route in routes) {
|
for (final RouteBase route in routes) {
|
||||||
if (route is GoRoute) {
|
if (route is GoRoute) {
|
||||||
final GlobalKey<NavigatorState>? parentKey =
|
final GlobalKey<NavigatorState>? parentKey = route.parentNavigatorKey;
|
||||||
route.parentNavigatorKey;
|
|
||||||
if (parentKey != null) {
|
if (parentKey != null) {
|
||||||
// Verify that the root navigator or a ShellRoute ancestor has a
|
// Verify that the root navigator or a ShellRoute ancestor has a
|
||||||
// matching navigator key.
|
// matching navigator key.
|
||||||
@ -57,7 +65,7 @@ class RouteConfiguration {
|
|||||||
" an ancestor ShellRoute's navigatorKey or GoRouter's"
|
" an ancestor ShellRoute's navigatorKey or GoRouter's"
|
||||||
' navigatorKey');
|
' navigatorKey');
|
||||||
|
|
||||||
checkParentNavigatorKeys(
|
_debugCheckParentNavigatorKeys(
|
||||||
route.routes,
|
route.routes,
|
||||||
<GlobalKey<NavigatorState>>[
|
<GlobalKey<NavigatorState>>[
|
||||||
// Once a parentNavigatorKey is used, only that navigator key
|
// Once a parentNavigatorKey is used, only that navigator key
|
||||||
@ -66,7 +74,7 @@ class RouteConfiguration {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
checkParentNavigatorKeys(
|
_debugCheckParentNavigatorKeys(
|
||||||
route.routes,
|
route.routes,
|
||||||
<GlobalKey<NavigatorState>>[
|
<GlobalKey<NavigatorState>>[
|
||||||
...allowedKeys,
|
...allowedKeys,
|
||||||
@ -74,20 +82,33 @@ class RouteConfiguration {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (route is ShellRoute && route.navigatorKey != null) {
|
} else if (route is ShellRoute && route.navigatorKey != null) {
|
||||||
checkParentNavigatorKeys(
|
_debugCheckParentNavigatorKeys(
|
||||||
route.routes,
|
route.routes,
|
||||||
<GlobalKey<NavigatorState>>[
|
<GlobalKey<NavigatorState>>[...allowedKeys..add(route.navigatorKey)],
|
||||||
...allowedKeys..add(route.navigatorKey)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkParentNavigatorKeys(
|
static bool _debugVerifyNoDuplicatePathParameter(
|
||||||
routes, <GlobalKey<NavigatorState>>[navigatorKey]);
|
List<RouteBase> routes, Map<String, GoRoute> usedPathParams) {
|
||||||
|
for (final RouteBase route in routes) {
|
||||||
|
if (route is! GoRoute) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (final String pathParam in route.pathParams) {
|
||||||
|
if (usedPathParams.containsKey(pathParam)) {
|
||||||
|
final bool sameRoute = usedPathParams[pathParam] == route;
|
||||||
|
throw GoError(
|
||||||
|
"duplicate path parameter, '$pathParam' found in ${sameRoute ? '$route' : '${usedPathParams[pathParam]}, and $route'}");
|
||||||
|
}
|
||||||
|
usedPathParams[pathParam] = route;
|
||||||
|
}
|
||||||
|
_debugVerifyNoDuplicatePathParameter(route.routes, usedPathParams);
|
||||||
|
route.pathParams.forEach(usedPathParams.remove);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The list of top level routes used by [GoRouterDelegate].
|
/// The list of top level routes used by [GoRouterDelegate].
|
||||||
|
@ -11,7 +11,6 @@ import 'builder.dart';
|
|||||||
import 'configuration.dart';
|
import 'configuration.dart';
|
||||||
import 'match.dart';
|
import 'match.dart';
|
||||||
import 'matching.dart';
|
import 'matching.dart';
|
||||||
import 'misc/errors.dart';
|
|
||||||
import 'typedefs.dart';
|
import 'typedefs.dart';
|
||||||
|
|
||||||
/// GoRouter implementation of [RouterDelegate].
|
/// GoRouter implementation of [RouterDelegate].
|
||||||
@ -44,7 +43,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
|||||||
/// Set to true to disable creating history entries on the web.
|
/// Set to true to disable creating history entries on the web.
|
||||||
final bool routerNeglect;
|
final bool routerNeglect;
|
||||||
|
|
||||||
RouteMatchList _matchList = RouteMatchList.empty();
|
RouteMatchList _matchList = RouteMatchList.empty;
|
||||||
|
|
||||||
/// Stores the number of times each route route has been pushed.
|
/// Stores the number of times each route route has been pushed.
|
||||||
///
|
///
|
||||||
@ -95,26 +94,21 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes the given location onto the page stack
|
/// Pushes the given location onto the page stack
|
||||||
void push(RouteMatch match) {
|
void push(RouteMatchList matches) {
|
||||||
if (match.route is ShellRoute) {
|
assert(matches.last.route is! ShellRoute);
|
||||||
throw GoError('ShellRoutes cannot be pushed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remap the pageKey to allow any number of the same page on the stack
|
// Remap the pageKey to allow any number of the same page on the stack
|
||||||
final String fullPath = match.fullpath;
|
final int count = (_pushCounts[matches.fullpath] ?? 0) + 1;
|
||||||
final int count = (_pushCounts[fullPath] ?? 0) + 1;
|
_pushCounts[matches.fullpath] = count;
|
||||||
_pushCounts[fullPath] = count;
|
final ValueKey<String> pageKey =
|
||||||
final ValueKey<String> pageKey = ValueKey<String>('$fullPath-p$count');
|
ValueKey<String>('${matches.fullpath}-p$count');
|
||||||
final RouteMatch newPageKeyMatch = RouteMatch(
|
final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch(
|
||||||
route: match.route,
|
route: matches.last.route,
|
||||||
subloc: match.subloc,
|
subloc: matches.last.subloc,
|
||||||
fullpath: match.fullpath,
|
extra: matches.last.extra,
|
||||||
encodedParams: match.encodedParams,
|
error: matches.last.error,
|
||||||
queryParams: match.queryParams,
|
|
||||||
queryParametersAll: match.queryParametersAll,
|
|
||||||
extra: match.extra,
|
|
||||||
error: match.error,
|
|
||||||
pageKey: pageKey,
|
pageKey: pageKey,
|
||||||
|
matches: matches,
|
||||||
);
|
);
|
||||||
|
|
||||||
_matchList.push(newPageKeyMatch);
|
_matchList.push(newPageKeyMatch);
|
||||||
@ -170,9 +164,9 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [push] which pushes the given location onto the page stack.
|
/// * [push] which pushes the given location onto the page stack.
|
||||||
void replace(RouteMatch match) {
|
void replace(RouteMatchList matches) {
|
||||||
_matchList.pop();
|
_matchList.pop();
|
||||||
push(match); // [push] will notify the listeners.
|
push(matches); // [push] will notify the listeners.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For internal use; visible for testing only.
|
/// For internal use; visible for testing only.
|
||||||
@ -209,3 +203,20 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
|||||||
return SynchronousFuture<void>(null);
|
return SynchronousFuture<void>(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The route match that represent route pushed through [GoRouter.push].
|
||||||
|
// TODO(chunhtai): Removes this once imperative API no longer insert route match.
|
||||||
|
class ImperativeRouteMatch extends RouteMatch {
|
||||||
|
/// Constructor for [ImperativeRouteMatch].
|
||||||
|
ImperativeRouteMatch({
|
||||||
|
required super.route,
|
||||||
|
required super.subloc,
|
||||||
|
required super.extra,
|
||||||
|
required super.error,
|
||||||
|
required super.pageKey,
|
||||||
|
required this.matches,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The matches that produces this route match.
|
||||||
|
final RouteMatchList matches;
|
||||||
|
}
|
||||||
|
@ -15,46 +15,25 @@ class RouteMatch {
|
|||||||
RouteMatch({
|
RouteMatch({
|
||||||
required this.route,
|
required this.route,
|
||||||
required this.subloc,
|
required this.subloc,
|
||||||
required this.fullpath,
|
|
||||||
required this.encodedParams,
|
|
||||||
required this.queryParams,
|
|
||||||
required this.queryParametersAll,
|
|
||||||
required this.extra,
|
required this.extra,
|
||||||
required this.error,
|
required this.error,
|
||||||
this.pageKey,
|
required this.pageKey,
|
||||||
}) : fullUriString = _addQueryParams(subloc, queryParametersAll),
|
});
|
||||||
assert(Uri.parse(subloc).queryParameters.isEmpty),
|
|
||||||
assert(Uri.parse(fullpath).queryParameters.isEmpty),
|
|
||||||
assert(() {
|
|
||||||
for (final MapEntry<String, String> p in encodedParams.entries) {
|
|
||||||
assert(p.value == Uri.encodeComponent(Uri.decodeComponent(p.value)),
|
|
||||||
'encodedParams[${p.key}] is not encoded properly: "${p.value}"');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
|
|
||||||
// ignore: public_member_api_docs
|
// ignore: public_member_api_docs
|
||||||
static RouteMatch? match({
|
static RouteMatch? match({
|
||||||
required RouteBase route,
|
required RouteBase route,
|
||||||
required String restLoc, // e.g. person/p1
|
required String restLoc, // e.g. person/p1
|
||||||
required String parentSubloc, // e.g. /family/f2
|
required String parentSubloc, // e.g. /family/f2
|
||||||
required String fullpath, // e.g. /family/:fid/person/:pid
|
required Map<String, String> pathParameters,
|
||||||
required Map<String, String> queryParams,
|
|
||||||
required Map<String, List<String>> queryParametersAll,
|
|
||||||
required Object? extra,
|
required Object? extra,
|
||||||
}) {
|
}) {
|
||||||
if (route is ShellRoute) {
|
if (route is ShellRoute) {
|
||||||
return RouteMatch(
|
return RouteMatch(
|
||||||
route: route,
|
route: route,
|
||||||
subloc: restLoc,
|
subloc: restLoc,
|
||||||
fullpath: '',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: queryParams,
|
|
||||||
queryParametersAll: queryParametersAll,
|
|
||||||
extra: extra,
|
extra: extra,
|
||||||
error: null,
|
error: null,
|
||||||
// Provide a unique pageKey to ensure that the page for this ShellRoute is
|
|
||||||
// reused.
|
|
||||||
pageKey: ValueKey<String>(route.hashCode.toString()),
|
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||||
);
|
);
|
||||||
} else if (route is GoRoute) {
|
} else if (route is GoRoute) {
|
||||||
@ -66,17 +45,17 @@ class RouteMatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, String> encodedParams = route.extractPathParams(match);
|
final Map<String, String> encodedParams = route.extractPathParams(match);
|
||||||
|
for (final MapEntry<String, String> param in encodedParams.entries) {
|
||||||
|
pathParameters[param.key] = Uri.decodeComponent(param.value);
|
||||||
|
}
|
||||||
final String pathLoc = patternToPath(route.path, encodedParams);
|
final String pathLoc = patternToPath(route.path, encodedParams);
|
||||||
final String subloc = concatenatePaths(parentSubloc, pathLoc);
|
final String subloc = concatenatePaths(parentSubloc, pathLoc);
|
||||||
return RouteMatch(
|
return RouteMatch(
|
||||||
route: route,
|
route: route,
|
||||||
subloc: subloc,
|
subloc: subloc,
|
||||||
fullpath: fullpath,
|
|
||||||
encodedParams: encodedParams,
|
|
||||||
queryParams: queryParams,
|
|
||||||
queryParametersAll: queryParametersAll,
|
|
||||||
extra: extra,
|
extra: extra,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw MatcherError('Unexpected route type: $route', restLoc);
|
throw MatcherError('Unexpected route type: $route', restLoc);
|
||||||
@ -88,41 +67,6 @@ class RouteMatch {
|
|||||||
/// The matched location.
|
/// The matched location.
|
||||||
final String subloc; // e.g. /family/f2
|
final String subloc; // e.g. /family/f2
|
||||||
|
|
||||||
/// The matched template.
|
|
||||||
final String fullpath; // e.g. /family/:fid
|
|
||||||
|
|
||||||
/// Parameters for the matched route, URI-encoded.
|
|
||||||
final Map<String, String> encodedParams;
|
|
||||||
|
|
||||||
/// The URI query split into a map according to the rules specified for FORM
|
|
||||||
/// post in the [HTML 4.01 specification section
|
|
||||||
/// 17.13.4](https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
|
|
||||||
/// "HTML 4.01 section 17.13.4").
|
|
||||||
///
|
|
||||||
/// If a key occurs more than once in the query string, it is mapped to an
|
|
||||||
/// arbitrary choice of possible value.
|
|
||||||
///
|
|
||||||
/// If the request is `a/b/?q1=v1&q2=v2&q2=v3`, then [queryParameter] will be
|
|
||||||
/// `{q1: 'v1', q2: 'v2'}`.
|
|
||||||
///
|
|
||||||
/// See also
|
|
||||||
/// * [queryParametersAll] that can provide a map that maps keys to all of
|
|
||||||
/// their values.
|
|
||||||
final Map<String, String> queryParams;
|
|
||||||
|
|
||||||
/// Returns the URI query split into a map according to the rules specified
|
|
||||||
/// for FORM post in the [HTML 4.01 specification section
|
|
||||||
/// 17.13.4](https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
|
|
||||||
/// "HTML 4.01 section 17.13.4").
|
|
||||||
///
|
|
||||||
/// Keys are mapped to lists of their values. If a key occurs only once, its
|
|
||||||
/// value is a singleton list. If a key occurs with no value, the empty string
|
|
||||||
/// is used as the value for that occurrence.
|
|
||||||
///
|
|
||||||
/// If the request is `a/b/?q1=v1&q2=v2&q2=v3`, then [queryParameterAll] with
|
|
||||||
/// be `{q1: ['v1'], q2: ['v2', 'v3']}`.
|
|
||||||
final Map<String, List<String>> queryParametersAll;
|
|
||||||
|
|
||||||
/// An extra object to pass along with the navigation.
|
/// An extra object to pass along with the navigation.
|
||||||
final Object? extra;
|
final Object? extra;
|
||||||
|
|
||||||
@ -130,29 +74,5 @@ class RouteMatch {
|
|||||||
final Exception? error;
|
final Exception? error;
|
||||||
|
|
||||||
/// Optional value key of type string, to hold a unique reference to a page.
|
/// Optional value key of type string, to hold a unique reference to a page.
|
||||||
final ValueKey<String>? pageKey;
|
final ValueKey<String> pageKey;
|
||||||
|
|
||||||
/// The full uri string
|
|
||||||
final String fullUriString; // e.g. /family/12?query=14
|
|
||||||
|
|
||||||
static String _addQueryParams(
|
|
||||||
String loc, Map<String, dynamic> queryParametersAll) {
|
|
||||||
final Uri uri = Uri.parse(loc);
|
|
||||||
assert(uri.queryParameters.isEmpty);
|
|
||||||
return Uri(
|
|
||||||
path: uri.path,
|
|
||||||
queryParameters:
|
|
||||||
queryParametersAll.isEmpty ? null : queryParametersAll)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters for the matched route, URI-decoded.
|
|
||||||
Map<String, String> get decodedParams => <String, String>{
|
|
||||||
for (final MapEntry<String, String> param in encodedParams.entries)
|
|
||||||
param.key: Uri.decodeComponent(param.value)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// For use by the Router architecture as part of the RouteMatch
|
|
||||||
@override
|
|
||||||
String toString() => 'RouteMatch($fullpath, $encodedParams)';
|
|
||||||
}
|
}
|
||||||
|
@ -18,27 +18,27 @@ class RouteMatcher {
|
|||||||
|
|
||||||
/// Finds the routes that matched the given URL.
|
/// Finds the routes that matched the given URL.
|
||||||
RouteMatchList findMatch(String location, {Object? extra}) {
|
RouteMatchList findMatch(String location, {Object? extra}) {
|
||||||
final String canonicalLocation = canonicalUri(location);
|
final Uri uri = Uri.parse(canonicalUri(location));
|
||||||
|
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
final List<RouteMatch> matches =
|
final List<RouteMatch> matches =
|
||||||
_getLocRouteMatches(canonicalLocation, extra);
|
_getLocRouteMatches(uri, extra, pathParameters);
|
||||||
return RouteMatchList(matches);
|
return RouteMatchList(matches, uri, pathParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RouteMatch> _getLocRouteMatches(String location, Object? extra) {
|
List<RouteMatch> _getLocRouteMatches(
|
||||||
final Uri uri = Uri.parse(location);
|
Uri uri, Object? extra, Map<String, String> pathParameters) {
|
||||||
final List<RouteMatch> result = _getLocRouteRecursively(
|
final List<RouteMatch>? result = _getLocRouteRecursively(
|
||||||
loc: uri.path,
|
loc: uri.path,
|
||||||
restLoc: uri.path,
|
restLoc: uri.path,
|
||||||
routes: configuration.routes,
|
routes: configuration.routes,
|
||||||
parentFullpath: '',
|
|
||||||
parentSubloc: '',
|
parentSubloc: '',
|
||||||
queryParams: uri.queryParameters,
|
pathParameters: pathParameters,
|
||||||
queryParametersAll: uri.queryParametersAll,
|
|
||||||
extra: extra,
|
extra: extra,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isEmpty) {
|
if (result == null) {
|
||||||
throw MatcherError('no routes for location', location);
|
throw MatcherError('no routes for location', uri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -48,23 +48,48 @@ class RouteMatcher {
|
|||||||
/// The list of [RouteMatch] objects.
|
/// The list of [RouteMatch] objects.
|
||||||
class RouteMatchList {
|
class RouteMatchList {
|
||||||
/// RouteMatchList constructor.
|
/// RouteMatchList constructor.
|
||||||
RouteMatchList(this._matches);
|
RouteMatchList(List<RouteMatch> matches, this.uri, this.pathParameters)
|
||||||
|
: _matches = matches,
|
||||||
|
fullpath = _generateFullPath(matches);
|
||||||
|
|
||||||
/// Constructs an empty matches object.
|
/// Constructs an empty matches object.
|
||||||
factory RouteMatchList.empty() => RouteMatchList(<RouteMatch>[]);
|
static RouteMatchList empty =
|
||||||
|
RouteMatchList(<RouteMatch>[], Uri.parse(''), const <String, String>{});
|
||||||
|
|
||||||
|
static String _generateFullPath(List<RouteMatch> matches) {
|
||||||
|
final StringBuffer buffer = StringBuffer();
|
||||||
|
bool addsSlash = false;
|
||||||
|
for (final RouteMatch match in matches) {
|
||||||
|
final RouteBase route = match.route;
|
||||||
|
if (route is GoRoute) {
|
||||||
|
if (addsSlash) {
|
||||||
|
buffer.write('/');
|
||||||
|
}
|
||||||
|
buffer.write(route.path);
|
||||||
|
addsSlash = addsSlash || route.path != '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
final List<RouteMatch> _matches;
|
final List<RouteMatch> _matches;
|
||||||
|
|
||||||
|
/// the full path pattern that matches the uri.
|
||||||
|
/// /family/:fid/person/:pid
|
||||||
|
final String fullpath;
|
||||||
|
|
||||||
|
/// Parameters for the matched route, URI-encoded.
|
||||||
|
final Map<String, String> pathParameters;
|
||||||
|
|
||||||
|
/// The uri of the current match.
|
||||||
|
final Uri uri;
|
||||||
|
|
||||||
/// Returns true if there are no matches.
|
/// Returns true if there are no matches.
|
||||||
bool get isEmpty => _matches.isEmpty;
|
bool get isEmpty => _matches.isEmpty;
|
||||||
|
|
||||||
/// Returns true if there are matches.
|
/// Returns true if there are matches.
|
||||||
bool get isNotEmpty => _matches.isNotEmpty;
|
bool get isNotEmpty => _matches.isNotEmpty;
|
||||||
|
|
||||||
/// The original URL that was matched.
|
|
||||||
Uri get location =>
|
|
||||||
_matches.isEmpty ? Uri() : Uri.parse(_matches.last.fullUriString);
|
|
||||||
|
|
||||||
/// Pushes a match onto the list of matches.
|
/// Pushes a match onto the list of matches.
|
||||||
void push(RouteMatch match) {
|
void push(RouteMatch match) {
|
||||||
_matches.add(match);
|
_matches.add(match);
|
||||||
@ -113,38 +138,25 @@ class MatcherError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RouteMatch> _getLocRouteRecursively({
|
List<RouteMatch>? _getLocRouteRecursively({
|
||||||
required String loc,
|
required String loc,
|
||||||
required String restLoc,
|
required String restLoc,
|
||||||
required String parentSubloc,
|
required String parentSubloc,
|
||||||
required List<RouteBase> routes,
|
required List<RouteBase> routes,
|
||||||
required String parentFullpath,
|
required Map<String, String> pathParameters,
|
||||||
required Map<String, String> queryParams,
|
|
||||||
required Map<String, List<String>> queryParametersAll,
|
|
||||||
required Object? extra,
|
required Object? extra,
|
||||||
}) {
|
}) {
|
||||||
bool debugGatherAllMatches = false;
|
List<RouteMatch>? result;
|
||||||
assert(() {
|
late Map<String, String> subPathParameters;
|
||||||
debugGatherAllMatches = true;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
final List<List<RouteMatch>> result = <List<RouteMatch>>[];
|
|
||||||
// find the set of matches at this level of the tree
|
// find the set of matches at this level of the tree
|
||||||
for (final RouteBase route in routes) {
|
for (final RouteBase route in routes) {
|
||||||
late final String fullpath;
|
subPathParameters = <String, String>{};
|
||||||
if (route is GoRoute) {
|
|
||||||
fullpath = concatenatePaths(parentFullpath, route.path);
|
|
||||||
} else if (route is ShellRoute) {
|
|
||||||
fullpath = parentFullpath;
|
|
||||||
}
|
|
||||||
|
|
||||||
final RouteMatch? match = RouteMatch.match(
|
final RouteMatch? match = RouteMatch.match(
|
||||||
route: route,
|
route: route,
|
||||||
restLoc: restLoc,
|
restLoc: restLoc,
|
||||||
parentSubloc: parentSubloc,
|
parentSubloc: parentSubloc,
|
||||||
fullpath: fullpath,
|
pathParameters: subPathParameters,
|
||||||
queryParams: queryParams,
|
|
||||||
queryParametersAll: queryParametersAll,
|
|
||||||
extra: extra,
|
extra: extra,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -157,7 +169,7 @@ List<RouteMatch> _getLocRouteRecursively({
|
|||||||
// If it is a complete match, then return the matched route
|
// If it is a complete match, then return the matched route
|
||||||
// NOTE: need a lower case match because subloc is canonicalized to match
|
// NOTE: need a lower case match because subloc is canonicalized to match
|
||||||
// the path case whereas the location can be of any case and still match
|
// the path case whereas the location can be of any case and still match
|
||||||
result.add(<RouteMatch>[match]);
|
result = <RouteMatch>[match];
|
||||||
} else if (route.routes.isEmpty) {
|
} else if (route.routes.isEmpty) {
|
||||||
// If it is partial match but no sub-routes, bail.
|
// If it is partial match but no sub-routes, bail.
|
||||||
continue;
|
continue;
|
||||||
@ -177,51 +189,37 @@ List<RouteMatch> _getLocRouteRecursively({
|
|||||||
newParentSubLoc = match.subloc;
|
newParentSubLoc = match.subloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<RouteMatch> subRouteMatch = _getLocRouteRecursively(
|
final List<RouteMatch>? subRouteMatch = _getLocRouteRecursively(
|
||||||
loc: loc,
|
loc: loc,
|
||||||
restLoc: childRestLoc,
|
restLoc: childRestLoc,
|
||||||
parentSubloc: newParentSubLoc,
|
parentSubloc: newParentSubLoc,
|
||||||
routes: route.routes,
|
routes: route.routes,
|
||||||
parentFullpath: fullpath,
|
pathParameters: subPathParameters,
|
||||||
queryParams: queryParams,
|
|
||||||
queryParametersAll: queryParametersAll,
|
|
||||||
extra: extra,
|
extra: extra,
|
||||||
).toList();
|
);
|
||||||
|
|
||||||
// If there's no sub-route matches, there is no match for this location
|
// If there's no sub-route matches, there is no match for this location
|
||||||
if (subRouteMatch.isEmpty) {
|
if (subRouteMatch == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
result.add(<RouteMatch>[match, ...subRouteMatch]);
|
result = <RouteMatch>[match, ...subRouteMatch];
|
||||||
}
|
}
|
||||||
// Should only reach here if there is a match.
|
// Should only reach here if there is a match.
|
||||||
if (debugGatherAllMatches) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (result != null) {
|
||||||
|
pathParameters.addAll(subPathParameters);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
if (result.isEmpty) {
|
|
||||||
return <RouteMatch>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are multiple routes that match the location, returning the first one.
|
|
||||||
// To make predefined routes to take precedence over dynamic routes eg. '/:id'
|
|
||||||
// consider adding the dynamic route at the end of the routes
|
|
||||||
return result.first;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The match used when there is an error during parsing.
|
/// The match used when there is an error during parsing.
|
||||||
RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
||||||
final Exception error = Exception(errorMessage);
|
final Exception error = Exception(errorMessage);
|
||||||
return RouteMatchList(<RouteMatch>[
|
return RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
subloc: uri.path,
|
subloc: uri.path,
|
||||||
fullpath: uri.path,
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: uri.queryParameters,
|
|
||||||
queryParametersAll: uri.queryParametersAll,
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: error,
|
error: error,
|
||||||
route: GoRoute(
|
route: GoRoute(
|
||||||
@ -230,6 +228,9 @@ RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
pageKey: const ValueKey<String>('error'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
uri,
|
||||||
|
const <String, String>{});
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
|
|||||||
|
|
||||||
// If there is a matching error for the initial location, we should
|
// If there is a matching error for the initial location, we should
|
||||||
// still try to process the top-level redirects.
|
// still try to process the top-level redirects.
|
||||||
initialMatches = RouteMatchList.empty();
|
initialMatches = RouteMatchList.empty;
|
||||||
}
|
}
|
||||||
Future<RouteMatchList> processRedirectorResult(RouteMatchList matches) {
|
Future<RouteMatchList> processRedirectorResult(RouteMatchList matches) {
|
||||||
if (matches.isEmpty) {
|
if (matches.isEmpty) {
|
||||||
@ -99,7 +99,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
|
|||||||
@override
|
@override
|
||||||
RouteInformation restoreRouteInformation(RouteMatchList configuration) {
|
RouteInformation restoreRouteInformation(RouteMatchList configuration) {
|
||||||
return RouteInformation(
|
return RouteInformation(
|
||||||
location: configuration.location.toString(),
|
location: configuration.uri.toString(),
|
||||||
state: configuration.extra,
|
state: configuration.extra,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,13 @@ FutureOr<RouteMatchList> redirect(
|
|||||||
{List<RouteMatchList>? redirectHistory,
|
{List<RouteMatchList>? redirectHistory,
|
||||||
Object? extra}) {
|
Object? extra}) {
|
||||||
FutureOr<RouteMatchList> processRedirect(RouteMatchList prevMatchList) {
|
FutureOr<RouteMatchList> processRedirect(RouteMatchList prevMatchList) {
|
||||||
final String prevLocation = prevMatchList.location.toString();
|
final String prevLocation = prevMatchList.uri.toString();
|
||||||
FutureOr<RouteMatchList> processTopLevelRedirect(
|
FutureOr<RouteMatchList> processTopLevelRedirect(
|
||||||
String? topRedirectLocation) {
|
String? topRedirectLocation) {
|
||||||
if (topRedirectLocation != null && topRedirectLocation != prevLocation) {
|
if (topRedirectLocation != null && topRedirectLocation != prevLocation) {
|
||||||
final RouteMatchList newMatch = _getNewMatches(
|
final RouteMatchList newMatch = _getNewMatches(
|
||||||
topRedirectLocation,
|
topRedirectLocation,
|
||||||
prevMatchList.location,
|
prevMatchList.uri,
|
||||||
configuration,
|
configuration,
|
||||||
matcher,
|
matcher,
|
||||||
redirectHistory!,
|
redirectHistory!,
|
||||||
@ -50,24 +50,13 @@ FutureOr<RouteMatchList> redirect(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge new params to keep params from previously matched paths, e.g.
|
|
||||||
// /users/:userId/book/:bookId provides userId and bookId to bookgit /:bookId
|
|
||||||
Map<String, String> previouslyMatchedParams = <String, String>{};
|
|
||||||
for (final RouteMatch match in prevMatchList.matches) {
|
|
||||||
assert(
|
|
||||||
!previouslyMatchedParams.keys.any(match.encodedParams.containsKey),
|
|
||||||
'Duplicated parameter names',
|
|
||||||
);
|
|
||||||
match.encodedParams.addAll(previouslyMatchedParams);
|
|
||||||
previouslyMatchedParams = match.encodedParams;
|
|
||||||
}
|
|
||||||
FutureOr<RouteMatchList> processRouteLevelRedirect(
|
FutureOr<RouteMatchList> processRouteLevelRedirect(
|
||||||
String? routeRedirectLocation) {
|
String? routeRedirectLocation) {
|
||||||
if (routeRedirectLocation != null &&
|
if (routeRedirectLocation != null &&
|
||||||
routeRedirectLocation != prevLocation) {
|
routeRedirectLocation != prevLocation) {
|
||||||
final RouteMatchList newMatch = _getNewMatches(
|
final RouteMatchList newMatch = _getNewMatches(
|
||||||
routeRedirectLocation,
|
routeRedirectLocation,
|
||||||
prevMatchList.location,
|
prevMatchList.uri,
|
||||||
configuration,
|
configuration,
|
||||||
matcher,
|
matcher,
|
||||||
redirectHistory!,
|
redirectHistory!,
|
||||||
@ -99,7 +88,6 @@ FutureOr<RouteMatchList> redirect(
|
|||||||
|
|
||||||
redirectHistory ??= <RouteMatchList>[prevMatchList];
|
redirectHistory ??= <RouteMatchList>[prevMatchList];
|
||||||
// Check for top-level redirect
|
// Check for top-level redirect
|
||||||
final Uri uri = prevMatchList.location;
|
|
||||||
final FutureOr<String?> topRedirectResult = configuration.topRedirect(
|
final FutureOr<String?> topRedirectResult = configuration.topRedirect(
|
||||||
context,
|
context,
|
||||||
GoRouterState(
|
GoRouterState(
|
||||||
@ -108,10 +96,11 @@ FutureOr<RouteMatchList> redirect(
|
|||||||
name: null,
|
name: null,
|
||||||
// No name available at the top level trim the query params off the
|
// No name available at the top level trim the query params off the
|
||||||
// sub-location to match route.redirect
|
// sub-location to match route.redirect
|
||||||
subloc: uri.path,
|
subloc: prevMatchList.uri.path,
|
||||||
queryParams: uri.queryParameters,
|
queryParams: prevMatchList.uri.queryParameters,
|
||||||
queryParametersAll: uri.queryParametersAll,
|
queryParametersAll: prevMatchList.uri.queryParametersAll,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
|
pageKey: const ValueKey<String>('topLevel'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -148,15 +137,16 @@ FutureOr<String?> _getRouteLevelRedirect(
|
|||||||
context,
|
context,
|
||||||
GoRouterState(
|
GoRouterState(
|
||||||
configuration,
|
configuration,
|
||||||
location: matchList.location.toString(),
|
location: matchList.uri.toString(),
|
||||||
subloc: match.subloc,
|
subloc: match.subloc,
|
||||||
name: route.name,
|
name: route.name,
|
||||||
path: route.path,
|
path: route.path,
|
||||||
fullpath: match.fullpath,
|
fullpath: matchList.fullpath,
|
||||||
extra: match.extra,
|
extra: match.extra,
|
||||||
params: match.decodedParams,
|
params: matchList.pathParameters,
|
||||||
queryParams: match.queryParams,
|
queryParams: matchList.uri.queryParameters,
|
||||||
queryParametersAll: match.queryParametersAll,
|
queryParametersAll: matchList.uri.queryParametersAll,
|
||||||
|
pageKey: match.pageKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -216,8 +206,8 @@ class RedirectionError extends Error implements UnsupportedError {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '${super.toString()} ${<String>[
|
String toString() => '${super.toString()} ${<String>[
|
||||||
...matches.map(
|
...matches
|
||||||
(RouteMatchList routeMatches) => routeMatches.location.toString()),
|
.map((RouteMatchList routeMatches) => routeMatches.uri.toString()),
|
||||||
].join(' => ')}';
|
].join(' => ')}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// 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:collection/collection.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'configuration.dart';
|
import 'configuration.dart';
|
||||||
import 'pages/custom_transition_page.dart';
|
import 'pages/custom_transition_page.dart';
|
||||||
@ -131,40 +131,14 @@ class GoRoute extends RouteBase {
|
|||||||
this.pageBuilder,
|
this.pageBuilder,
|
||||||
this.parentNavigatorKey,
|
this.parentNavigatorKey,
|
||||||
this.redirect,
|
this.redirect,
|
||||||
List<RouteBase> routes = const <RouteBase>[],
|
super.routes = const <RouteBase>[],
|
||||||
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
|
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
|
||||||
assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'),
|
assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'),
|
||||||
assert(pageBuilder != null || builder != null || redirect != null,
|
assert(pageBuilder != null || builder != null || redirect != null,
|
||||||
'builder, pageBuilder, or redirect must be provided'),
|
'builder, pageBuilder, or redirect must be provided'),
|
||||||
super._(
|
super._() {
|
||||||
routes: routes,
|
|
||||||
) {
|
|
||||||
// cache the path regexp and parameters
|
// cache the path regexp and parameters
|
||||||
_pathRE = patternToRegExp(path, _pathParams);
|
_pathRE = patternToRegExp(path, pathParams);
|
||||||
assert(() {
|
|
||||||
// check path params
|
|
||||||
final Map<String, List<String>> groupedParams =
|
|
||||||
_pathParams.groupListsBy<String>((String p) => p);
|
|
||||||
final Map<String, List<String>> dupParams =
|
|
||||||
Map<String, List<String>>.fromEntries(
|
|
||||||
groupedParams.entries
|
|
||||||
.where((MapEntry<String, List<String>> e) => e.value.length > 1),
|
|
||||||
);
|
|
||||||
assert(dupParams.isEmpty,
|
|
||||||
'duplicate path params: ${dupParams.keys.join(', ')}');
|
|
||||||
|
|
||||||
// check sub-routes
|
|
||||||
for (final RouteBase route in routes) {
|
|
||||||
// check paths
|
|
||||||
if (route is GoRoute) {
|
|
||||||
assert(
|
|
||||||
route.path == '/' ||
|
|
||||||
(!route.path.startsWith('/') && !route.path.endsWith('/')),
|
|
||||||
'sub-route path may not start or end with /: ${route.path}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional name of the route.
|
/// Optional name of the route.
|
||||||
@ -332,9 +306,16 @@ class GoRoute extends RouteBase {
|
|||||||
|
|
||||||
/// Extract the path parameters from a match.
|
/// Extract the path parameters from a match.
|
||||||
Map<String, String> extractPathParams(RegExpMatch match) =>
|
Map<String, String> extractPathParams(RegExpMatch match) =>
|
||||||
extractPathParameters(_pathParams, match);
|
extractPathParameters(pathParams, match);
|
||||||
|
|
||||||
final List<String> _pathParams = <String>[];
|
/// The path parameters in this route.
|
||||||
|
@internal
|
||||||
|
final List<String> pathParams = <String>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'GoRoute(name: $name, path: $path)';
|
||||||
|
}
|
||||||
|
|
||||||
late final RegExp _pathRE;
|
late final RegExp _pathRE;
|
||||||
}
|
}
|
||||||
|
@ -146,8 +146,18 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
|||||||
bool canPop() => _routerDelegate.canPop();
|
bool canPop() => _routerDelegate.canPop();
|
||||||
|
|
||||||
void _handleStateMayChange() {
|
void _handleStateMayChange() {
|
||||||
final String newLocation =
|
final String newLocation;
|
||||||
_routerDelegate.currentConfiguration.location.toString();
|
if (routerDelegate.currentConfiguration.isNotEmpty &&
|
||||||
|
routerDelegate.currentConfiguration.matches.last
|
||||||
|
is ImperativeRouteMatch) {
|
||||||
|
newLocation = (routerDelegate.currentConfiguration.matches.last
|
||||||
|
as ImperativeRouteMatch)
|
||||||
|
.matches
|
||||||
|
.uri
|
||||||
|
.toString();
|
||||||
|
} else {
|
||||||
|
newLocation = _routerDelegate.currentConfiguration.uri.toString();
|
||||||
|
}
|
||||||
if (_location != newLocation) {
|
if (_location != newLocation) {
|
||||||
_location = newLocation;
|
_location = newLocation;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -207,7 +217,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
|||||||
_routerDelegate.navigatorKey.currentContext!,
|
_routerDelegate.navigatorKey.currentContext!,
|
||||||
)
|
)
|
||||||
.then<void>((RouteMatchList matches) {
|
.then<void>((RouteMatchList matches) {
|
||||||
_routerDelegate.push(matches.last);
|
_routerDelegate.push(matches);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +249,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
|||||||
_routerDelegate.navigatorKey.currentContext!,
|
_routerDelegate.navigatorKey.currentContext!,
|
||||||
)
|
)
|
||||||
.then<void>((RouteMatchList matchList) {
|
.then<void>((RouteMatchList matchList) {
|
||||||
routerDelegate.replace(matchList.matches.last);
|
routerDelegate.replace(matchList);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import 'misc/errors.dart';
|
|||||||
@immutable
|
@immutable
|
||||||
class GoRouterState {
|
class GoRouterState {
|
||||||
/// Default constructor for creating route state during routing.
|
/// Default constructor for creating route state during routing.
|
||||||
GoRouterState(
|
const GoRouterState(
|
||||||
this._configuration, {
|
this._configuration, {
|
||||||
required this.location,
|
required this.location,
|
||||||
required this.subloc,
|
required this.subloc,
|
||||||
@ -26,13 +26,8 @@ class GoRouterState {
|
|||||||
this.queryParametersAll = const <String, List<String>>{},
|
this.queryParametersAll = const <String, List<String>>{},
|
||||||
this.extra,
|
this.extra,
|
||||||
this.error,
|
this.error,
|
||||||
ValueKey<String>? pageKey,
|
required this.pageKey,
|
||||||
}) : pageKey = pageKey ??
|
});
|
||||||
ValueKey<String>(error != null
|
|
||||||
? 'error'
|
|
||||||
: fullpath != null && fullpath.isNotEmpty
|
|
||||||
? fullpath
|
|
||||||
: subloc);
|
|
||||||
|
|
||||||
// TODO(johnpryan): remove once namedLocation is removed from go_router.
|
// TODO(johnpryan): remove once namedLocation is removed from go_router.
|
||||||
// See https://github.com/flutter/flutter/issues/107729
|
// See https://github.com/flutter/flutter/issues/107729
|
||||||
|
@ -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: 5.1.10
|
version: 5.2.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
|
||||||
|
|
||||||
|
@ -28,18 +28,18 @@ void main() {
|
|||||||
navigatorKey: GlobalKey<NavigatorState>(),
|
navigatorKey: GlobalKey<NavigatorState>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
final RouteMatchList matches = RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first as GoRoute,
|
route: config.routes.first as GoRoute,
|
||||||
subloc: '/',
|
subloc: '/',
|
||||||
fullpath: '/',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>('/'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
Uri.parse('/'),
|
||||||
|
const <String, String>{});
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
_BuilderTestWidget(
|
_BuilderTestWidget(
|
||||||
@ -75,18 +75,18 @@ void main() {
|
|||||||
navigatorKey: GlobalKey<NavigatorState>(),
|
navigatorKey: GlobalKey<NavigatorState>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
final RouteMatchList matches = RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first,
|
route: config.routes.first,
|
||||||
subloc: '/',
|
subloc: '/',
|
||||||
fullpath: '/',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>('/'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
Uri.parse('/'),
|
||||||
|
<String, String>{});
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
_BuilderTestWidget(
|
_BuilderTestWidget(
|
||||||
@ -117,18 +117,18 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
final RouteMatchList matches = RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first as GoRoute,
|
route: config.routes.first as GoRoute,
|
||||||
subloc: '/',
|
subloc: '/',
|
||||||
fullpath: '/',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>('/'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
Uri.parse('/'),
|
||||||
|
<String, String>{});
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
_BuilderTestWidget(
|
_BuilderTestWidget(
|
||||||
@ -172,28 +172,25 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
final RouteMatchList matches = RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first,
|
route: config.routes.first,
|
||||||
subloc: '',
|
subloc: '',
|
||||||
fullpath: '',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>(''),
|
||||||
),
|
),
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first.routes.first,
|
route: config.routes.first.routes.first,
|
||||||
subloc: '/details',
|
subloc: '/details',
|
||||||
fullpath: '/details',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>('/details'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
Uri.parse('/details'),
|
||||||
|
<String, String>{});
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
_BuilderTestWidget(
|
_BuilderTestWidget(
|
||||||
@ -250,18 +247,18 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
final RouteMatchList matches = RouteMatchList(
|
||||||
|
<RouteMatch>[
|
||||||
RouteMatch(
|
RouteMatch(
|
||||||
route: config.routes.first.routes.first as GoRoute,
|
route: config.routes.first.routes.first as GoRoute,
|
||||||
subloc: '/a/details',
|
subloc: '/a/details',
|
||||||
fullpath: '/a/details',
|
|
||||||
encodedParams: <String, String>{},
|
|
||||||
queryParams: <String, String>{},
|
|
||||||
queryParametersAll: <String, List<String>>{},
|
|
||||||
extra: null,
|
extra: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
pageKey: const ValueKey<String>('/a/details'),
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
|
Uri.parse('/a/details'),
|
||||||
|
<String, String>{});
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
_BuilderTestWidget(
|
_BuilderTestWidget(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
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 'package:go_router/src/delegate.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';
|
||||||
|
|
||||||
@ -62,10 +63,6 @@ void main() {
|
|||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final GoRouter goRouter = await createGoRouter(tester);
|
final GoRouter goRouter = await createGoRouter(tester);
|
||||||
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
||||||
expect(
|
|
||||||
goRouter.routerDelegate.matches.matches[0].pageKey,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
goRouter.push('/a');
|
goRouter.push('/a');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -113,8 +110,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('replace', () {
|
group('replace', () {
|
||||||
testWidgets(
|
testWidgets('It should replace the last match with the given one',
|
||||||
'It should replace the last match with the given one',
|
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final GoRouter goRouter = GoRouter(
|
final GoRouter goRouter = GoRouter(
|
||||||
initialLocation: '/',
|
initialLocation: '/',
|
||||||
@ -148,12 +144,15 @@ void main() {
|
|||||||
reason: 'The last match should have been removed',
|
reason: 'The last match should have been removed',
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
goRouter.routerDelegate.matches.last.fullpath,
|
(goRouter.routerDelegate.matches.last as ImperativeRouteMatch)
|
||||||
|
.matches
|
||||||
|
.uri
|
||||||
|
.toString(),
|
||||||
'/page-1',
|
'/page-1',
|
||||||
reason: 'The new location should have been pushed',
|
reason: 'The new location should have been pushed',
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'It should return different pageKey when replace is called',
|
'It should return different pageKey when replace is called',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
@ -161,7 +160,7 @@ void main() {
|
|||||||
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
||||||
expect(
|
expect(
|
||||||
goRouter.routerDelegate.matches.matches[0].pageKey,
|
goRouter.routerDelegate.matches.matches[0].pageKey,
|
||||||
null,
|
isNotNull,
|
||||||
);
|
);
|
||||||
|
|
||||||
goRouter.push('/a');
|
goRouter.push('/a');
|
||||||
@ -228,13 +227,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
goRouter.routerDelegate.matches.last,
|
goRouter.routerDelegate.matches.last,
|
||||||
isA<RouteMatch>()
|
isA<RouteMatch>().having(
|
||||||
.having(
|
|
||||||
(RouteMatch match) => match.fullpath,
|
|
||||||
'match.fullpath',
|
|
||||||
'/page-1',
|
|
||||||
)
|
|
||||||
.having(
|
|
||||||
(RouteMatch match) => (match.route as GoRoute).name,
|
(RouteMatch match) => (match.route as GoRoute).name,
|
||||||
'match.route.name',
|
'match.route.name',
|
||||||
'page1',
|
'page1',
|
||||||
|
@ -42,7 +42,7 @@ void main() {
|
|||||||
path: '/',
|
path: '/',
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
return Builder(builder: (BuildContext context) {
|
return Builder(builder: (BuildContext context) {
|
||||||
return Text(GoRouterState.of(context).location);
|
return Text('1 ${GoRouterState.of(context).location}');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
@ -50,7 +50,7 @@ void main() {
|
|||||||
path: 'a',
|
path: 'a',
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
return Builder(builder: (BuildContext context) {
|
return Builder(builder: (BuildContext context) {
|
||||||
return Text(GoRouterState.of(context).location);
|
return Text('2 ${GoRouterState.of(context).location}');
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@ -58,13 +58,13 @@ void main() {
|
|||||||
final GoRouter router = await createRouter(routes, tester);
|
final GoRouter router = await createRouter(routes, tester);
|
||||||
router.go('/?p=123');
|
router.go('/?p=123');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text('/?p=123'), findsOneWidget);
|
expect(find.text('1 /?p=123'), findsOneWidget);
|
||||||
|
|
||||||
router.go('/a');
|
router.go('/a');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text('/a'), findsOneWidget);
|
expect(find.text('2 /a'), findsOneWidget);
|
||||||
// The query parameter is removed, so is the location in first page.
|
// The query parameter is removed, so is the location in first page.
|
||||||
expect(find.text('/', skipOffstage: false), findsOneWidget);
|
expect(find.text('1 /a', skipOffstage: false), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('registry retains GoRouterState for exiting route',
|
testWidgets('registry retains GoRouterState for exiting route',
|
||||||
|
@ -9,7 +9,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.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 'package:go_router/src/delegate.dart';
|
||||||
import 'package:go_router/src/match.dart';
|
import 'package:go_router/src/match.dart';
|
||||||
|
import 'package:go_router/src/matching.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'test_helpers.dart';
|
import 'test_helpers.dart';
|
||||||
@ -43,24 +45,24 @@ void main() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
final GoRouter router = await createRouter(routes, tester);
|
final GoRouter router = await createRouter(routes, tester);
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(matches.first.fullpath, '/');
|
expect(matches.uri.toString(), '/');
|
||||||
expect(find.byType(HomeScreen), findsOneWidget);
|
expect(find.byType(HomeScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('If there is more than one route to match, use the first match',
|
testWidgets('If there is more than one route to match, use the first match',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final List<GoRoute> routes = <GoRoute>[
|
final List<GoRoute> routes = <GoRoute>[
|
||||||
GoRoute(path: '/', builder: dummy),
|
GoRoute(name: '1', path: '/', builder: dummy),
|
||||||
GoRoute(path: '/', builder: dummy),
|
GoRoute(name: '2', path: '/', builder: dummy),
|
||||||
];
|
];
|
||||||
|
|
||||||
final GoRouter router = await createRouter(routes, tester);
|
final GoRouter router = await createRouter(routes, tester);
|
||||||
router.go('/');
|
router.go('/');
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||||
expect(matches, hasLength(1));
|
expect(matches, hasLength(1));
|
||||||
expect(matches.first.fullpath, '/');
|
expect((matches.first.route as GoRoute).name, '1');
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ void main() {
|
|||||||
|
|
||||||
test('leading / on sub-route', () {
|
test('leading / on sub-route', () {
|
||||||
expect(() {
|
expect(() {
|
||||||
|
GoRouter(
|
||||||
|
routes: <RouteBase>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: dummy,
|
builder: dummy,
|
||||||
@ -81,12 +85,16 @@ void main() {
|
|||||||
builder: dummy,
|
builder: dummy,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}, throwsA(isAssertionError));
|
}, throwsA(isAssertionError));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trailing / on sub-route', () {
|
test('trailing / on sub-route', () {
|
||||||
expect(() {
|
expect(() {
|
||||||
|
GoRouter(
|
||||||
|
routes: <RouteBase>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: dummy,
|
builder: dummy,
|
||||||
@ -96,6 +104,8 @@ void main() {
|
|||||||
builder: dummy,
|
builder: dummy,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}, throwsA(isAssertionError));
|
}, throwsA(isAssertionError));
|
||||||
});
|
});
|
||||||
@ -328,44 +338,44 @@ void main() {
|
|||||||
|
|
||||||
final GoRouter router = await createRouter(routes, tester);
|
final GoRouter router = await createRouter(routes, tester);
|
||||||
{
|
{
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(matches.first.fullpath, '/');
|
expect(matches.uri.toString(), '/');
|
||||||
expect(find.byType(HomeScreen), findsOneWidget);
|
expect(find.byType(HomeScreen), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.go('/login');
|
router.go('/login');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
{
|
{
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches.length, 2);
|
expect(matches.matches.length, 2);
|
||||||
expect(matches.first.subloc, '/');
|
expect(matches.matches.first.subloc, '/');
|
||||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||||
expect(matches[1].subloc, '/login');
|
expect(matches.matches[1].subloc, '/login');
|
||||||
expect(find.byType(LoginScreen), findsOneWidget);
|
expect(find.byType(LoginScreen), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.go('/family/f2');
|
router.go('/family/f2');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
{
|
{
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches.length, 2);
|
expect(matches.matches.length, 2);
|
||||||
expect(matches.first.subloc, '/');
|
expect(matches.matches.first.subloc, '/');
|
||||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||||
expect(matches[1].subloc, '/family/f2');
|
expect(matches.matches[1].subloc, '/family/f2');
|
||||||
expect(find.byType(FamilyScreen), findsOneWidget);
|
expect(find.byType(FamilyScreen), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.go('/family/f2/person/p1');
|
router.go('/family/f2/person/p1');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
{
|
{
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches.length, 3);
|
expect(matches.matches.length, 3);
|
||||||
expect(matches.first.subloc, '/');
|
expect(matches.matches.first.subloc, '/');
|
||||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||||
expect(matches[1].subloc, '/family/f2');
|
expect(matches.matches[1].subloc, '/family/f2');
|
||||||
expect(find.byType(FamilyScreen, skipOffstage: false), findsOneWidget);
|
expect(find.byType(FamilyScreen, skipOffstage: false), findsOneWidget);
|
||||||
expect(matches[2].subloc, '/family/f2/person/p1');
|
expect(matches.matches[2].subloc, '/family/f2/person/p1');
|
||||||
expect(find.byType(PersonScreen), findsOneWidget);
|
expect(find.byType(PersonScreen), findsOneWidget);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1134,14 +1144,12 @@ void main() {
|
|||||||
final GoRouter router = await createRouter(routes, tester);
|
final GoRouter router = await createRouter(routes, tester);
|
||||||
final String loc = router
|
final String loc = router
|
||||||
.namedLocation('page1', params: <String, String>{'param1': param1});
|
.namedLocation('page1', params: <String, String>{'param1': param1});
|
||||||
log.info('loc= $loc');
|
|
||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
log.info('param1= ${matches.first.decodedParams['param1']}');
|
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
expect(matches.first.decodedParams['param1'], param1);
|
expect(matches.pathParameters['param1'], param1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('preserve query param spaces and slashes',
|
testWidgets('preserve query param spaces and slashes',
|
||||||
@ -1163,9 +1171,9 @@ void main() {
|
|||||||
queryParams: <String, String>{'param1': param1});
|
queryParams: <String, String>{'param1': param1});
|
||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
expect(matches.first.queryParams['param1'], param1);
|
expect(matches.uri.queryParameters['param1'], param1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1835,12 +1843,12 @@ void main() {
|
|||||||
final String loc = '/family/$fid';
|
final String loc = '/family/$fid';
|
||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
|
|
||||||
expect(router.location, loc);
|
expect(router.location, loc);
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(find.byType(FamilyScreen), findsOneWidget);
|
expect(find.byType(FamilyScreen), findsOneWidget);
|
||||||
expect(matches.first.decodedParams['fid'], fid);
|
expect(matches.pathParameters['fid'], fid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1864,12 +1872,12 @@ void main() {
|
|||||||
final String loc = '/family?fid=$fid';
|
final String loc = '/family?fid=$fid';
|
||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
|
|
||||||
expect(router.location, loc);
|
expect(router.location, loc);
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(find.byType(FamilyScreen), findsOneWidget);
|
expect(find.byType(FamilyScreen), findsOneWidget);
|
||||||
expect(matches.first.queryParams['fid'], fid);
|
expect(matches.uri.queryParameters['fid'], fid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1891,10 +1899,9 @@ void main() {
|
|||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
log.info('param1= ${matches.first.decodedParams['param1']}');
|
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
expect(matches.first.decodedParams['param1'], param1);
|
expect(matches.pathParameters['param1'], param1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('preserve query param spaces and slashes',
|
testWidgets('preserve query param spaces and slashes',
|
||||||
@ -1914,17 +1921,17 @@ void main() {
|
|||||||
router.go('/page1?param1=$param1');
|
router.go('/page1?param1=$param1');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
expect(matches.first.queryParams['param1'], param1);
|
expect(matches.uri.queryParameters['param1'], param1);
|
||||||
|
|
||||||
final String loc = '/page1?param1=${Uri.encodeQueryComponent(param1)}';
|
final String loc = '/page1?param1=${Uri.encodeQueryComponent(param1)}';
|
||||||
router.go(loc);
|
router.go(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final List<RouteMatch> matches2 = router.routerDelegate.matches.matches;
|
final RouteMatchList matches2 = router.routerDelegate.matches;
|
||||||
expect(find.byType(DummyScreen), findsOneWidget);
|
expect(find.byType(DummyScreen), findsOneWidget);
|
||||||
expect(matches2[0].queryParams['param1'], param1);
|
expect(matches2.uri.queryParameters['param1'], param1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('error: duplicate path param', () {
|
test('error: duplicate path param', () {
|
||||||
@ -1963,9 +1970,9 @@ void main() {
|
|||||||
tester,
|
tester,
|
||||||
initialLocation: '/?id=0&id=1',
|
initialLocation: '/?id=0&id=1',
|
||||||
);
|
);
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(matches.first.fullpath, '/');
|
expect(matches.fullpath, '/');
|
||||||
expect(find.byType(HomeScreen), findsOneWidget);
|
expect(find.byType(HomeScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1986,9 +1993,9 @@ void main() {
|
|||||||
|
|
||||||
router.go('/0?id=1');
|
router.go('/0?id=1');
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
expect(matches, hasLength(1));
|
expect(matches.matches, hasLength(1));
|
||||||
expect(matches.first.fullpath, '/:id');
|
expect(matches.fullpath, '/:id');
|
||||||
expect(find.byType(HomeScreen), findsOneWidget);
|
expect(find.byType(HomeScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2098,13 +2105,15 @@ void main() {
|
|||||||
|
|
||||||
router.push(loc);
|
router.push(loc);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
final RouteMatchList matches = router.routerDelegate.matches;
|
||||||
|
|
||||||
expect(router.location, loc);
|
expect(router.location, loc);
|
||||||
expect(matches, hasLength(2));
|
expect(matches.matches, hasLength(2));
|
||||||
expect(find.byType(PersonScreen), findsOneWidget);
|
expect(find.byType(PersonScreen), findsOneWidget);
|
||||||
expect(matches.last.decodedParams['fid'], fid);
|
final ImperativeRouteMatch imperativeRouteMatch =
|
||||||
expect(matches.last.decodedParams['pid'], pid);
|
matches.matches.last as ImperativeRouteMatch;
|
||||||
|
expect(imperativeRouteMatch.matches.pathParameters['fid'], fid);
|
||||||
|
expect(imperativeRouteMatch.matches.pathParameters['pid'], pid);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('goNames should allow dynamics values for queryParams',
|
testWidgets('goNames should allow dynamics values for queryParams',
|
||||||
|
@ -14,47 +14,36 @@ void main() {
|
|||||||
path: '/users/:userId',
|
path: '/users/:userId',
|
||||||
builder: _builder,
|
builder: _builder,
|
||||||
);
|
);
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
final RouteMatch? match = RouteMatch.match(
|
final RouteMatch? match = RouteMatch.match(
|
||||||
route: route,
|
route: route,
|
||||||
restLoc: '/users/123',
|
restLoc: '/users/123',
|
||||||
parentSubloc: '',
|
parentSubloc: '',
|
||||||
fullpath: '/users/:userId',
|
pathParameters: pathParameters,
|
||||||
queryParams: <String, String>{},
|
|
||||||
extra: const _Extra('foo'),
|
extra: const _Extra('foo'),
|
||||||
queryParametersAll: <String, List<String>>{
|
|
||||||
'bar': <String>['baz', 'biz'],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
fail('Null match');
|
fail('Null match');
|
||||||
}
|
}
|
||||||
expect(match.route, route);
|
expect(match.route, route);
|
||||||
expect(match.subloc, '/users/123');
|
expect(match.subloc, '/users/123');
|
||||||
expect(match.fullpath, '/users/:userId');
|
expect(pathParameters['userId'], '123');
|
||||||
expect(match.encodedParams['userId'], '123');
|
|
||||||
expect(match.queryParams['foo'], isNull);
|
|
||||||
expect(match.queryParametersAll['bar'], <String>['baz', 'biz']);
|
|
||||||
expect(match.extra, const _Extra('foo'));
|
expect(match.extra, const _Extra('foo'));
|
||||||
expect(match.error, isNull);
|
expect(match.error, isNull);
|
||||||
expect(match.pageKey, isNull);
|
expect(match.pageKey, isNotNull);
|
||||||
expect(match.fullUriString, '/users/123?bar=baz&bar=biz');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('subloc', () {
|
test('subloc', () {
|
||||||
final GoRoute route = GoRoute(
|
final GoRoute route = GoRoute(
|
||||||
path: 'users/:userId',
|
path: 'users/:userId',
|
||||||
builder: _builder,
|
builder: _builder,
|
||||||
);
|
);
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
final RouteMatch? match = RouteMatch.match(
|
final RouteMatch? match = RouteMatch.match(
|
||||||
route: route,
|
route: route,
|
||||||
restLoc: 'users/123',
|
restLoc: 'users/123',
|
||||||
parentSubloc: '/home',
|
parentSubloc: '/home',
|
||||||
fullpath: '/home/users/:userId',
|
pathParameters: pathParameters,
|
||||||
queryParams: <String, String>{
|
|
||||||
'foo': 'bar',
|
|
||||||
},
|
|
||||||
queryParametersAll: <String, List<String>>{
|
|
||||||
'foo': <String>['bar'],
|
|
||||||
},
|
|
||||||
extra: const _Extra('foo'),
|
extra: const _Extra('foo'),
|
||||||
);
|
);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
@ -62,14 +51,12 @@ void main() {
|
|||||||
}
|
}
|
||||||
expect(match.route, route);
|
expect(match.route, route);
|
||||||
expect(match.subloc, '/home/users/123');
|
expect(match.subloc, '/home/users/123');
|
||||||
expect(match.fullpath, '/home/users/:userId');
|
expect(pathParameters['userId'], '123');
|
||||||
expect(match.encodedParams['userId'], '123');
|
|
||||||
expect(match.queryParams['foo'], 'bar');
|
|
||||||
expect(match.extra, const _Extra('foo'));
|
expect(match.extra, const _Extra('foo'));
|
||||||
expect(match.error, isNull);
|
expect(match.error, isNull);
|
||||||
expect(match.pageKey, isNull);
|
expect(match.pageKey, isNotNull);
|
||||||
expect(match.fullUriString, '/home/users/123?foo=bar');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ShellRoute has a unique pageKey', () {
|
test('ShellRoute has a unique pageKey', () {
|
||||||
final ShellRoute route = ShellRoute(
|
final ShellRoute route = ShellRoute(
|
||||||
builder: _shellBuilder,
|
builder: _shellBuilder,
|
||||||
@ -80,17 +67,12 @@ void main() {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
final RouteMatch? match = RouteMatch.match(
|
final RouteMatch? match = RouteMatch.match(
|
||||||
route: route,
|
route: route,
|
||||||
restLoc: 'users/123',
|
restLoc: 'users/123',
|
||||||
parentSubloc: '/home',
|
parentSubloc: '/home',
|
||||||
fullpath: '/home/users/:userId',
|
pathParameters: pathParameters,
|
||||||
queryParams: <String, String>{
|
|
||||||
'foo': 'bar',
|
|
||||||
},
|
|
||||||
queryParametersAll: <String, List<String>>{
|
|
||||||
'foo': <String>['bar'],
|
|
||||||
},
|
|
||||||
extra: const _Extra('foo'),
|
extra: const _Extra('foo'),
|
||||||
);
|
);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
@ -98,6 +80,61 @@ void main() {
|
|||||||
}
|
}
|
||||||
expect(match.pageKey, isNotNull);
|
expect(match.pageKey, isNotNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ShellRoute Match has stable unique key', () {
|
||||||
|
final ShellRoute route = ShellRoute(
|
||||||
|
builder: _shellBuilder,
|
||||||
|
routes: <GoRoute>[
|
||||||
|
GoRoute(
|
||||||
|
path: '/users/:userId',
|
||||||
|
builder: _builder,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
|
final RouteMatch? match1 = RouteMatch.match(
|
||||||
|
route: route,
|
||||||
|
restLoc: 'users/123',
|
||||||
|
parentSubloc: '/home',
|
||||||
|
pathParameters: pathParameters,
|
||||||
|
extra: const _Extra('foo'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final RouteMatch? match2 = RouteMatch.match(
|
||||||
|
route: route,
|
||||||
|
restLoc: 'users/1234',
|
||||||
|
parentSubloc: '/home',
|
||||||
|
pathParameters: pathParameters,
|
||||||
|
extra: const _Extra('foo1'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(match1!.pageKey, match2!.pageKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GoRoute Match has stable unique key', () {
|
||||||
|
final GoRoute route = GoRoute(
|
||||||
|
path: 'users/:userId',
|
||||||
|
builder: _builder,
|
||||||
|
);
|
||||||
|
final Map<String, String> pathParameters = <String, String>{};
|
||||||
|
final RouteMatch? match1 = RouteMatch.match(
|
||||||
|
route: route,
|
||||||
|
restLoc: 'users/123',
|
||||||
|
parentSubloc: '/home',
|
||||||
|
pathParameters: pathParameters,
|
||||||
|
extra: const _Extra('foo'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final RouteMatch? match2 = RouteMatch.match(
|
||||||
|
route: route,
|
||||||
|
restLoc: 'users/1234',
|
||||||
|
parentSubloc: '/home',
|
||||||
|
pathParameters: pathParameters,
|
||||||
|
extra: const _Extra('foo1'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(match1!.pageKey, match2!.pageKey);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,9 +56,8 @@ void main() {
|
|||||||
const RouteInformation(location: '/'), context);
|
const RouteInformation(location: '/'), context);
|
||||||
List<RouteMatch> matches = matchesObj.matches;
|
List<RouteMatch> matches = matchesObj.matches;
|
||||||
expect(matches.length, 1);
|
expect(matches.length, 1);
|
||||||
expect(matches[0].queryParams.isEmpty, isTrue);
|
expect(matchesObj.uri.toString(), '/');
|
||||||
expect(matches[0].extra, isNull);
|
expect(matches[0].extra, isNull);
|
||||||
expect(matches[0].fullUriString, '/');
|
|
||||||
expect(matches[0].subloc, '/');
|
expect(matches[0].subloc, '/');
|
||||||
expect(matches[0].route, routes[0]);
|
expect(matches[0].route, routes[0]);
|
||||||
|
|
||||||
@ -67,17 +66,12 @@ void main() {
|
|||||||
RouteInformation(location: '/abc?def=ghi', state: extra), context);
|
RouteInformation(location: '/abc?def=ghi', state: extra), context);
|
||||||
matches = matchesObj.matches;
|
matches = matchesObj.matches;
|
||||||
expect(matches.length, 2);
|
expect(matches.length, 2);
|
||||||
expect(matches[0].queryParams.length, 1);
|
expect(matchesObj.uri.toString(), '/abc?def=ghi');
|
||||||
expect(matches[0].queryParams['def'], 'ghi');
|
|
||||||
expect(matches[0].extra, extra);
|
expect(matches[0].extra, extra);
|
||||||
expect(matches[0].fullUriString, '/?def=ghi');
|
|
||||||
expect(matches[0].subloc, '/');
|
expect(matches[0].subloc, '/');
|
||||||
expect(matches[0].route, routes[0]);
|
expect(matches[0].route, routes[0]);
|
||||||
|
|
||||||
expect(matches[1].queryParams.length, 1);
|
|
||||||
expect(matches[1].queryParams['def'], 'ghi');
|
|
||||||
expect(matches[1].extra, extra);
|
expect(matches[1].extra, extra);
|
||||||
expect(matches[1].fullUriString, '/abc?def=ghi');
|
|
||||||
expect(matches[1].subloc, '/abc');
|
expect(matches[1].subloc, '/abc');
|
||||||
expect(matches[1].route, routes[0].routes[0]);
|
expect(matches[1].route, routes[0].routes[0]);
|
||||||
});
|
});
|
||||||
@ -195,9 +189,8 @@ void main() {
|
|||||||
const RouteInformation(location: '/def'), context);
|
const RouteInformation(location: '/def'), context);
|
||||||
final List<RouteMatch> matches = matchesObj.matches;
|
final List<RouteMatch> matches = matchesObj.matches;
|
||||||
expect(matches.length, 1);
|
expect(matches.length, 1);
|
||||||
expect(matches[0].queryParams.isEmpty, isTrue);
|
expect(matchesObj.uri.toString(), '/def');
|
||||||
expect(matches[0].extra, isNull);
|
expect(matches[0].extra, isNull);
|
||||||
expect(matches[0].fullUriString, '/def');
|
|
||||||
expect(matches[0].subloc, '/def');
|
expect(matches[0].subloc, '/def');
|
||||||
expect(matches[0].error!.toString(),
|
expect(matches[0].error!.toString(),
|
||||||
'Exception: no routes for location: /def');
|
'Exception: no routes for location: /def');
|
||||||
@ -231,18 +224,15 @@ void main() {
|
|||||||
final List<RouteMatch> matches = matchesObj.matches;
|
final List<RouteMatch> matches = matchesObj.matches;
|
||||||
|
|
||||||
expect(matches.length, 2);
|
expect(matches.length, 2);
|
||||||
expect(matches[0].queryParams.isEmpty, isTrue);
|
expect(matchesObj.uri.toString(), '/123/family/456');
|
||||||
|
expect(matchesObj.pathParameters.length, 2);
|
||||||
|
expect(matchesObj.pathParameters['uid'], '123');
|
||||||
|
expect(matchesObj.pathParameters['fid'], '456');
|
||||||
expect(matches[0].extra, isNull);
|
expect(matches[0].extra, isNull);
|
||||||
expect(matches[0].fullUriString, '/');
|
|
||||||
expect(matches[0].subloc, '/');
|
expect(matches[0].subloc, '/');
|
||||||
|
|
||||||
expect(matches[1].queryParams.isEmpty, isTrue);
|
|
||||||
expect(matches[1].extra, isNull);
|
expect(matches[1].extra, isNull);
|
||||||
expect(matches[1].fullUriString, '/123/family/456');
|
|
||||||
expect(matches[1].subloc, '/123/family/456');
|
expect(matches[1].subloc, '/123/family/456');
|
||||||
expect(matches[1].encodedParams.length, 2);
|
|
||||||
expect(matches[1].encodedParams['uid'], '123');
|
|
||||||
expect(matches[1].encodedParams['fid'], '456');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
@ -279,10 +269,9 @@ void main() {
|
|||||||
final List<RouteMatch> matches = matchesObj.matches;
|
final List<RouteMatch> matches = matchesObj.matches;
|
||||||
|
|
||||||
expect(matches.length, 2);
|
expect(matches.length, 2);
|
||||||
expect(matches[0].fullUriString, '/');
|
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||||
expect(matches[0].subloc, '/');
|
expect(matches[0].subloc, '/');
|
||||||
|
|
||||||
expect(matches[1].fullUriString, '/123/family/345');
|
|
||||||
expect(matches[1].subloc, '/123/family/345');
|
expect(matches[1].subloc, '/123/family/345');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,10 +309,9 @@ void main() {
|
|||||||
final List<RouteMatch> matches = matchesObj.matches;
|
final List<RouteMatch> matches = matchesObj.matches;
|
||||||
|
|
||||||
expect(matches.length, 2);
|
expect(matches.length, 2);
|
||||||
expect(matches[0].fullUriString, '/');
|
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||||
expect(matches[0].subloc, '/');
|
expect(matches[0].subloc, '/');
|
||||||
|
|
||||||
expect(matches[1].fullUriString, '/123/family/345');
|
|
||||||
expect(matches[1].subloc, '/123/family/345');
|
expect(matches[1].subloc, '/123/family/345');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user