mirror of
https://github.com/flutter/packages.git
synced 2025-07-03 09:08:54 +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
|
||||
|
||||
- Fixes link of ShellRoute in README.
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'logging.dart';
|
||||
import 'match.dart';
|
||||
import 'matching.dart';
|
||||
@ -75,11 +76,7 @@ class RouteBuilder {
|
||||
registry: _registry, child: result);
|
||||
} on _RouteBuilderError catch (e) {
|
||||
return _buildErrorNavigator(
|
||||
context,
|
||||
e,
|
||||
Uri.parse(matchList.location.toString()),
|
||||
pop,
|
||||
configuration.navigatorKey);
|
||||
context, e, matchList.uri, pop, configuration.navigatorKey);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -124,13 +121,12 @@ class RouteBuilder {
|
||||
try {
|
||||
final Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPage =
|
||||
<GlobalKey<NavigatorState>, List<Page<Object?>>>{};
|
||||
final Map<String, String> params = <String, String>{};
|
||||
_buildRecursive(context, matchList, 0, onPop, routerNeglect, keyToPage,
|
||||
params, navigatorKey, registry);
|
||||
navigatorKey, registry);
|
||||
return keyToPage[navigatorKey]!;
|
||||
} on _RouteBuilderError catch (e) {
|
||||
return <Page<Object?>>[
|
||||
_buildErrorPage(context, e, matchList.location),
|
||||
_buildErrorPage(context, e, matchList.uri),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -142,7 +138,6 @@ class RouteBuilder {
|
||||
VoidCallback pop,
|
||||
bool routerNeglect,
|
||||
Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPages,
|
||||
Map<String, String> params,
|
||||
GlobalKey<NavigatorState> navigatorKey,
|
||||
Map<Page<Object?>, GoRouterState> registry,
|
||||
) {
|
||||
@ -157,11 +152,7 @@ class RouteBuilder {
|
||||
}
|
||||
|
||||
final RouteBase route = match.route;
|
||||
final Map<String, String> newParams = <String, String>{
|
||||
...params,
|
||||
...match.decodedParams
|
||||
};
|
||||
final GoRouterState state = buildState(match, newParams);
|
||||
final GoRouterState state = buildState(matchList, match);
|
||||
if (route is GoRoute) {
|
||||
final Page<Object?> page = _buildPageForRoute(context, state, match);
|
||||
registry[page] = state;
|
||||
@ -173,7 +164,7 @@ class RouteBuilder {
|
||||
keyToPages.putIfAbsent(goRouteNavKey, () => <Page<Object?>>[]).add(page);
|
||||
|
||||
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
||||
keyToPages, newParams, navigatorKey, registry);
|
||||
keyToPages, navigatorKey, registry);
|
||||
} else if (route is ShellRoute) {
|
||||
// The key for the Navigator that will display this ShellRoute's page.
|
||||
final GlobalKey<NavigatorState> parentNavigatorKey = navigatorKey;
|
||||
@ -194,7 +185,7 @@ class RouteBuilder {
|
||||
|
||||
// Build the remaining pages
|
||||
_buildRecursive(context, matchList, startIndex + 1, pop, routerNeglect,
|
||||
keyToPages, newParams, shellNavigatorKey, registry);
|
||||
keyToPages, shellNavigatorKey, registry);
|
||||
|
||||
// Build the Navigator
|
||||
final Widget child = _buildNavigator(
|
||||
@ -235,25 +226,27 @@ class RouteBuilder {
|
||||
/// Helper method that builds a [GoRouterState] object for the given [match]
|
||||
/// and [params].
|
||||
@visibleForTesting
|
||||
GoRouterState buildState(RouteMatch match, Map<String, String> params) {
|
||||
GoRouterState buildState(RouteMatchList matchList, RouteMatch match) {
|
||||
final RouteBase route = match.route;
|
||||
String? name = '';
|
||||
String? name;
|
||||
String path = '';
|
||||
if (route is GoRoute) {
|
||||
name = route.name;
|
||||
path = route.path;
|
||||
}
|
||||
final RouteMatchList effectiveMatchList =
|
||||
match is ImperativeRouteMatch ? match.matches : matchList;
|
||||
return GoRouterState(
|
||||
configuration,
|
||||
location: match.fullUriString,
|
||||
location: effectiveMatchList.uri.toString(),
|
||||
subloc: match.subloc,
|
||||
name: name,
|
||||
path: path,
|
||||
fullpath: match.fullpath,
|
||||
params: params,
|
||||
fullpath: effectiveMatchList.fullpath,
|
||||
params: effectiveMatchList.pathParameters,
|
||||
error: match.error,
|
||||
queryParams: match.queryParams,
|
||||
queryParametersAll: match.queryParametersAll,
|
||||
queryParams: effectiveMatchList.uri.queryParameters,
|
||||
queryParametersAll: effectiveMatchList.uri.queryParametersAll,
|
||||
extra: match.extra,
|
||||
pageKey: match.pageKey,
|
||||
);
|
||||
@ -425,6 +418,7 @@ class RouteBuilder {
|
||||
queryParams: uri.queryParameters,
|
||||
queryParametersAll: uri.queryParametersAll,
|
||||
error: Exception(error),
|
||||
pageKey: const ValueKey<String>('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 'logging.dart';
|
||||
import 'misc/errors.dart';
|
||||
import 'path_utils.dart';
|
||||
import 'typedefs.dart';
|
||||
|
||||
export 'route.dart';
|
||||
export 'state.dart';
|
||||
|
||||
@ -20,34 +20,42 @@ class RouteConfiguration {
|
||||
required this.redirectLimit,
|
||||
required this.topRedirect,
|
||||
required this.navigatorKey,
|
||||
}) {
|
||||
}) : assert(_debugCheckPath(routes, true)),
|
||||
assert(
|
||||
_debugVerifyNoDuplicatePathParameter(routes, <String, GoRoute>{})),
|
||||
assert(_debugCheckParentNavigatorKeys(
|
||||
routes, <GlobalKey<NavigatorState>>[navigatorKey])) {
|
||||
_cacheNameToPath('', routes);
|
||||
|
||||
log.info(_debugKnownRoutes());
|
||||
}
|
||||
|
||||
assert(() {
|
||||
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) {
|
||||
static bool _debugCheckPath(List<RouteBase> routes, bool isTopLevel) {
|
||||
for (final RouteBase route in routes) {
|
||||
late bool subRouteIsTopLevel;
|
||||
if (route is GoRoute) {
|
||||
if (isTopLevel) {
|
||||
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
|
||||
// navigatorKey or the root navigator key.
|
||||
void checkParentNavigatorKeys(
|
||||
static bool _debugCheckParentNavigatorKeys(
|
||||
List<RouteBase> routes, List<GlobalKey<NavigatorState>> allowedKeys) {
|
||||
for (final RouteBase route in routes) {
|
||||
if (route is GoRoute) {
|
||||
final GlobalKey<NavigatorState>? parentKey =
|
||||
route.parentNavigatorKey;
|
||||
final GlobalKey<NavigatorState>? parentKey = route.parentNavigatorKey;
|
||||
if (parentKey != null) {
|
||||
// Verify that the root navigator or a ShellRoute ancestor has a
|
||||
// matching navigator key.
|
||||
@ -57,7 +65,7 @@ class RouteConfiguration {
|
||||
" an ancestor ShellRoute's navigatorKey or GoRouter's"
|
||||
' navigatorKey');
|
||||
|
||||
checkParentNavigatorKeys(
|
||||
_debugCheckParentNavigatorKeys(
|
||||
route.routes,
|
||||
<GlobalKey<NavigatorState>>[
|
||||
// Once a parentNavigatorKey is used, only that navigator key
|
||||
@ -66,7 +74,7 @@ class RouteConfiguration {
|
||||
],
|
||||
);
|
||||
} else {
|
||||
checkParentNavigatorKeys(
|
||||
_debugCheckParentNavigatorKeys(
|
||||
route.routes,
|
||||
<GlobalKey<NavigatorState>>[
|
||||
...allowedKeys,
|
||||
@ -74,20 +82,33 @@ class RouteConfiguration {
|
||||
);
|
||||
}
|
||||
} else if (route is ShellRoute && route.navigatorKey != null) {
|
||||
checkParentNavigatorKeys(
|
||||
_debugCheckParentNavigatorKeys(
|
||||
route.routes,
|
||||
<GlobalKey<NavigatorState>>[
|
||||
...allowedKeys..add(route.navigatorKey)
|
||||
],
|
||||
<GlobalKey<NavigatorState>>[...allowedKeys..add(route.navigatorKey)],
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkParentNavigatorKeys(
|
||||
routes, <GlobalKey<NavigatorState>>[navigatorKey]);
|
||||
static bool _debugVerifyNoDuplicatePathParameter(
|
||||
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;
|
||||
}());
|
||||
}
|
||||
|
||||
/// The list of top level routes used by [GoRouterDelegate].
|
||||
|
@ -11,7 +11,6 @@ import 'builder.dart';
|
||||
import 'configuration.dart';
|
||||
import 'match.dart';
|
||||
import 'matching.dart';
|
||||
import 'misc/errors.dart';
|
||||
import 'typedefs.dart';
|
||||
|
||||
/// GoRouter implementation of [RouterDelegate].
|
||||
@ -44,7 +43,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
/// Set to true to disable creating history entries on the web.
|
||||
final bool routerNeglect;
|
||||
|
||||
RouteMatchList _matchList = RouteMatchList.empty();
|
||||
RouteMatchList _matchList = RouteMatchList.empty;
|
||||
|
||||
/// 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
|
||||
void push(RouteMatch match) {
|
||||
if (match.route is ShellRoute) {
|
||||
throw GoError('ShellRoutes cannot be pushed');
|
||||
}
|
||||
void push(RouteMatchList matches) {
|
||||
assert(matches.last.route is! ShellRoute);
|
||||
|
||||
// Remap the pageKey to allow any number of the same page on the stack
|
||||
final String fullPath = match.fullpath;
|
||||
final int count = (_pushCounts[fullPath] ?? 0) + 1;
|
||||
_pushCounts[fullPath] = count;
|
||||
final ValueKey<String> pageKey = ValueKey<String>('$fullPath-p$count');
|
||||
final RouteMatch newPageKeyMatch = RouteMatch(
|
||||
route: match.route,
|
||||
subloc: match.subloc,
|
||||
fullpath: match.fullpath,
|
||||
encodedParams: match.encodedParams,
|
||||
queryParams: match.queryParams,
|
||||
queryParametersAll: match.queryParametersAll,
|
||||
extra: match.extra,
|
||||
error: match.error,
|
||||
final int count = (_pushCounts[matches.fullpath] ?? 0) + 1;
|
||||
_pushCounts[matches.fullpath] = count;
|
||||
final ValueKey<String> pageKey =
|
||||
ValueKey<String>('${matches.fullpath}-p$count');
|
||||
final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch(
|
||||
route: matches.last.route,
|
||||
subloc: matches.last.subloc,
|
||||
extra: matches.last.extra,
|
||||
error: matches.last.error,
|
||||
pageKey: pageKey,
|
||||
matches: matches,
|
||||
);
|
||||
|
||||
_matchList.push(newPageKeyMatch);
|
||||
@ -170,9 +164,9 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
///
|
||||
/// See also:
|
||||
/// * [push] which pushes the given location onto the page stack.
|
||||
void replace(RouteMatch match) {
|
||||
void replace(RouteMatchList matches) {
|
||||
_matchList.pop();
|
||||
push(match); // [push] will notify the listeners.
|
||||
push(matches); // [push] will notify the listeners.
|
||||
}
|
||||
|
||||
/// For internal use; visible for testing only.
|
||||
@ -209,3 +203,20 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
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({
|
||||
required this.route,
|
||||
required this.subloc,
|
||||
required this.fullpath,
|
||||
required this.encodedParams,
|
||||
required this.queryParams,
|
||||
required this.queryParametersAll,
|
||||
required this.extra,
|
||||
required this.error,
|
||||
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;
|
||||
}());
|
||||
required this.pageKey,
|
||||
});
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
static RouteMatch? match({
|
||||
required RouteBase route,
|
||||
required String restLoc, // e.g. person/p1
|
||||
required String parentSubloc, // e.g. /family/f2
|
||||
required String fullpath, // e.g. /family/:fid/person/:pid
|
||||
required Map<String, String> queryParams,
|
||||
required Map<String, List<String>> queryParametersAll,
|
||||
required Map<String, String> pathParameters,
|
||||
required Object? extra,
|
||||
}) {
|
||||
if (route is ShellRoute) {
|
||||
return RouteMatch(
|
||||
route: route,
|
||||
subloc: restLoc,
|
||||
fullpath: '',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: queryParams,
|
||||
queryParametersAll: queryParametersAll,
|
||||
extra: extra,
|
||||
error: null,
|
||||
// Provide a unique pageKey to ensure that the page for this ShellRoute is
|
||||
// reused.
|
||||
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||
);
|
||||
} else if (route is GoRoute) {
|
||||
@ -66,17 +45,17 @@ class RouteMatch {
|
||||
}
|
||||
|
||||
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 subloc = concatenatePaths(parentSubloc, pathLoc);
|
||||
return RouteMatch(
|
||||
route: route,
|
||||
subloc: subloc,
|
||||
fullpath: fullpath,
|
||||
encodedParams: encodedParams,
|
||||
queryParams: queryParams,
|
||||
queryParametersAll: queryParametersAll,
|
||||
extra: extra,
|
||||
error: null,
|
||||
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||
);
|
||||
}
|
||||
throw MatcherError('Unexpected route type: $route', restLoc);
|
||||
@ -88,41 +67,6 @@ class RouteMatch {
|
||||
/// The matched location.
|
||||
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.
|
||||
final Object? extra;
|
||||
|
||||
@ -130,29 +74,5 @@ class RouteMatch {
|
||||
final Exception? error;
|
||||
|
||||
/// Optional value key of type string, to hold a unique reference to a page.
|
||||
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)';
|
||||
final ValueKey<String> pageKey;
|
||||
}
|
||||
|
@ -18,27 +18,27 @@ class RouteMatcher {
|
||||
|
||||
/// Finds the routes that matched the given URL.
|
||||
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 =
|
||||
_getLocRouteMatches(canonicalLocation, extra);
|
||||
return RouteMatchList(matches);
|
||||
_getLocRouteMatches(uri, extra, pathParameters);
|
||||
return RouteMatchList(matches, uri, pathParameters);
|
||||
}
|
||||
|
||||
List<RouteMatch> _getLocRouteMatches(String location, Object? extra) {
|
||||
final Uri uri = Uri.parse(location);
|
||||
final List<RouteMatch> result = _getLocRouteRecursively(
|
||||
List<RouteMatch> _getLocRouteMatches(
|
||||
Uri uri, Object? extra, Map<String, String> pathParameters) {
|
||||
final List<RouteMatch>? result = _getLocRouteRecursively(
|
||||
loc: uri.path,
|
||||
restLoc: uri.path,
|
||||
routes: configuration.routes,
|
||||
parentFullpath: '',
|
||||
parentSubloc: '',
|
||||
queryParams: uri.queryParameters,
|
||||
queryParametersAll: uri.queryParametersAll,
|
||||
pathParameters: pathParameters,
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
if (result.isEmpty) {
|
||||
throw MatcherError('no routes for location', location);
|
||||
if (result == null) {
|
||||
throw MatcherError('no routes for location', uri.toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -48,23 +48,48 @@ class RouteMatcher {
|
||||
/// The list of [RouteMatch] objects.
|
||||
class RouteMatchList {
|
||||
/// RouteMatchList constructor.
|
||||
RouteMatchList(this._matches);
|
||||
RouteMatchList(List<RouteMatch> matches, this.uri, this.pathParameters)
|
||||
: _matches = matches,
|
||||
fullpath = _generateFullPath(matches);
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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.
|
||||
bool get isEmpty => _matches.isEmpty;
|
||||
|
||||
/// Returns true if there are matches.
|
||||
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.
|
||||
void push(RouteMatch match) {
|
||||
_matches.add(match);
|
||||
@ -113,38 +138,25 @@ class MatcherError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
List<RouteMatch> _getLocRouteRecursively({
|
||||
List<RouteMatch>? _getLocRouteRecursively({
|
||||
required String loc,
|
||||
required String restLoc,
|
||||
required String parentSubloc,
|
||||
required List<RouteBase> routes,
|
||||
required String parentFullpath,
|
||||
required Map<String, String> queryParams,
|
||||
required Map<String, List<String>> queryParametersAll,
|
||||
required Map<String, String> pathParameters,
|
||||
required Object? extra,
|
||||
}) {
|
||||
bool debugGatherAllMatches = false;
|
||||
assert(() {
|
||||
debugGatherAllMatches = true;
|
||||
return true;
|
||||
}());
|
||||
final List<List<RouteMatch>> result = <List<RouteMatch>>[];
|
||||
List<RouteMatch>? result;
|
||||
late Map<String, String> subPathParameters;
|
||||
// find the set of matches at this level of the tree
|
||||
for (final RouteBase route in routes) {
|
||||
late final String fullpath;
|
||||
if (route is GoRoute) {
|
||||
fullpath = concatenatePaths(parentFullpath, route.path);
|
||||
} else if (route is ShellRoute) {
|
||||
fullpath = parentFullpath;
|
||||
}
|
||||
subPathParameters = <String, String>{};
|
||||
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: restLoc,
|
||||
parentSubloc: parentSubloc,
|
||||
fullpath: fullpath,
|
||||
queryParams: queryParams,
|
||||
queryParametersAll: queryParametersAll,
|
||||
pathParameters: subPathParameters,
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -157,7 +169,7 @@ List<RouteMatch> _getLocRouteRecursively({
|
||||
// If it is a complete match, then return the matched route
|
||||
// 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
|
||||
result.add(<RouteMatch>[match]);
|
||||
result = <RouteMatch>[match];
|
||||
} else if (route.routes.isEmpty) {
|
||||
// If it is partial match but no sub-routes, bail.
|
||||
continue;
|
||||
@ -177,51 +189,37 @@ List<RouteMatch> _getLocRouteRecursively({
|
||||
newParentSubLoc = match.subloc;
|
||||
}
|
||||
|
||||
final List<RouteMatch> subRouteMatch = _getLocRouteRecursively(
|
||||
final List<RouteMatch>? subRouteMatch = _getLocRouteRecursively(
|
||||
loc: loc,
|
||||
restLoc: childRestLoc,
|
||||
parentSubloc: newParentSubLoc,
|
||||
routes: route.routes,
|
||||
parentFullpath: fullpath,
|
||||
queryParams: queryParams,
|
||||
queryParametersAll: queryParametersAll,
|
||||
pathParameters: subPathParameters,
|
||||
extra: extra,
|
||||
).toList();
|
||||
);
|
||||
|
||||
// If there's no sub-route matches, there is no match for this location
|
||||
if (subRouteMatch.isEmpty) {
|
||||
if (subRouteMatch == null) {
|
||||
continue;
|
||||
}
|
||||
result.add(<RouteMatch>[match, ...subRouteMatch]);
|
||||
result = <RouteMatch>[match, ...subRouteMatch];
|
||||
}
|
||||
// Should only reach here if there is a match.
|
||||
if (debugGatherAllMatches) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (result != null) {
|
||||
pathParameters.addAll(subPathParameters);
|
||||
}
|
||||
|
||||
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;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// The match used when there is an error during parsing.
|
||||
RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
||||
final Exception error = Exception(errorMessage);
|
||||
return RouteMatchList(<RouteMatch>[
|
||||
return RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
subloc: uri.path,
|
||||
fullpath: uri.path,
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: uri.queryParameters,
|
||||
queryParametersAll: uri.queryParametersAll,
|
||||
extra: null,
|
||||
error: error,
|
||||
route: GoRoute(
|
||||
@ -230,6 +228,9 @@ RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
||||
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
|
||||
// still try to process the top-level redirects.
|
||||
initialMatches = RouteMatchList.empty();
|
||||
initialMatches = RouteMatchList.empty;
|
||||
}
|
||||
Future<RouteMatchList> processRedirectorResult(RouteMatchList matches) {
|
||||
if (matches.isEmpty) {
|
||||
@ -99,7 +99,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteMatchList configuration) {
|
||||
return RouteInformation(
|
||||
location: configuration.location.toString(),
|
||||
location: configuration.uri.toString(),
|
||||
state: configuration.extra,
|
||||
);
|
||||
}
|
||||
|
@ -26,13 +26,13 @@ FutureOr<RouteMatchList> redirect(
|
||||
{List<RouteMatchList>? redirectHistory,
|
||||
Object? extra}) {
|
||||
FutureOr<RouteMatchList> processRedirect(RouteMatchList prevMatchList) {
|
||||
final String prevLocation = prevMatchList.location.toString();
|
||||
final String prevLocation = prevMatchList.uri.toString();
|
||||
FutureOr<RouteMatchList> processTopLevelRedirect(
|
||||
String? topRedirectLocation) {
|
||||
if (topRedirectLocation != null && topRedirectLocation != prevLocation) {
|
||||
final RouteMatchList newMatch = _getNewMatches(
|
||||
topRedirectLocation,
|
||||
prevMatchList.location,
|
||||
prevMatchList.uri,
|
||||
configuration,
|
||||
matcher,
|
||||
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(
|
||||
String? routeRedirectLocation) {
|
||||
if (routeRedirectLocation != null &&
|
||||
routeRedirectLocation != prevLocation) {
|
||||
final RouteMatchList newMatch = _getNewMatches(
|
||||
routeRedirectLocation,
|
||||
prevMatchList.location,
|
||||
prevMatchList.uri,
|
||||
configuration,
|
||||
matcher,
|
||||
redirectHistory!,
|
||||
@ -99,7 +88,6 @@ FutureOr<RouteMatchList> redirect(
|
||||
|
||||
redirectHistory ??= <RouteMatchList>[prevMatchList];
|
||||
// Check for top-level redirect
|
||||
final Uri uri = prevMatchList.location;
|
||||
final FutureOr<String?> topRedirectResult = configuration.topRedirect(
|
||||
context,
|
||||
GoRouterState(
|
||||
@ -108,10 +96,11 @@ FutureOr<RouteMatchList> redirect(
|
||||
name: null,
|
||||
// No name available at the top level trim the query params off the
|
||||
// sub-location to match route.redirect
|
||||
subloc: uri.path,
|
||||
queryParams: uri.queryParameters,
|
||||
queryParametersAll: uri.queryParametersAll,
|
||||
subloc: prevMatchList.uri.path,
|
||||
queryParams: prevMatchList.uri.queryParameters,
|
||||
queryParametersAll: prevMatchList.uri.queryParametersAll,
|
||||
extra: extra,
|
||||
pageKey: const ValueKey<String>('topLevel'),
|
||||
),
|
||||
);
|
||||
|
||||
@ -148,15 +137,16 @@ FutureOr<String?> _getRouteLevelRedirect(
|
||||
context,
|
||||
GoRouterState(
|
||||
configuration,
|
||||
location: matchList.location.toString(),
|
||||
location: matchList.uri.toString(),
|
||||
subloc: match.subloc,
|
||||
name: route.name,
|
||||
path: route.path,
|
||||
fullpath: match.fullpath,
|
||||
fullpath: matchList.fullpath,
|
||||
extra: match.extra,
|
||||
params: match.decodedParams,
|
||||
queryParams: match.queryParams,
|
||||
queryParametersAll: match.queryParametersAll,
|
||||
params: matchList.pathParameters,
|
||||
queryParams: matchList.uri.queryParameters,
|
||||
queryParametersAll: matchList.uri.queryParametersAll,
|
||||
pageKey: match.pageKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -216,8 +206,8 @@ class RedirectionError extends Error implements UnsupportedError {
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()} ${<String>[
|
||||
...matches.map(
|
||||
(RouteMatchList routeMatches) => routeMatches.location.toString()),
|
||||
...matches
|
||||
.map((RouteMatchList routeMatches) => routeMatches.uri.toString()),
|
||||
].join(' => ')}';
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'pages/custom_transition_page.dart';
|
||||
@ -131,40 +131,14 @@ class GoRoute extends RouteBase {
|
||||
this.pageBuilder,
|
||||
this.parentNavigatorKey,
|
||||
this.redirect,
|
||||
List<RouteBase> routes = const <RouteBase>[],
|
||||
super.routes = const <RouteBase>[],
|
||||
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
|
||||
assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'),
|
||||
assert(pageBuilder != null || builder != null || redirect != null,
|
||||
'builder, pageBuilder, or redirect must be provided'),
|
||||
super._(
|
||||
routes: routes,
|
||||
) {
|
||||
super._() {
|
||||
// cache the path regexp and parameters
|
||||
_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;
|
||||
}());
|
||||
_pathRE = patternToRegExp(path, pathParams);
|
||||
}
|
||||
|
||||
/// Optional name of the route.
|
||||
@ -332,9 +306,16 @@ class GoRoute extends RouteBase {
|
||||
|
||||
/// Extract the path parameters from a 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;
|
||||
}
|
||||
|
@ -146,8 +146,18 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
bool canPop() => _routerDelegate.canPop();
|
||||
|
||||
void _handleStateMayChange() {
|
||||
final String newLocation =
|
||||
_routerDelegate.currentConfiguration.location.toString();
|
||||
final String newLocation;
|
||||
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) {
|
||||
_location = newLocation;
|
||||
notifyListeners();
|
||||
@ -207,7 +217,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
_routerDelegate.navigatorKey.currentContext!,
|
||||
)
|
||||
.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!,
|
||||
)
|
||||
.then<void>((RouteMatchList matchList) {
|
||||
routerDelegate.replace(matchList.matches.last);
|
||||
routerDelegate.replace(matchList);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import 'misc/errors.dart';
|
||||
@immutable
|
||||
class GoRouterState {
|
||||
/// Default constructor for creating route state during routing.
|
||||
GoRouterState(
|
||||
const GoRouterState(
|
||||
this._configuration, {
|
||||
required this.location,
|
||||
required this.subloc,
|
||||
@ -26,13 +26,8 @@ class GoRouterState {
|
||||
this.queryParametersAll = const <String, List<String>>{},
|
||||
this.extra,
|
||||
this.error,
|
||||
ValueKey<String>? pageKey,
|
||||
}) : pageKey = pageKey ??
|
||||
ValueKey<String>(error != null
|
||||
? 'error'
|
||||
: fullpath != null && fullpath.isNotEmpty
|
||||
? fullpath
|
||||
: subloc);
|
||||
required this.pageKey,
|
||||
});
|
||||
|
||||
// TODO(johnpryan): remove once namedLocation is removed from go_router.
|
||||
// See https://github.com/flutter/flutter/issues/107729
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: go_router
|
||||
description: A declarative router for Flutter based on Navigation 2 supporting
|
||||
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
|
||||
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>(),
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first as GoRoute,
|
||||
subloc: '/',
|
||||
fullpath: '/',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
]);
|
||||
],
|
||||
Uri.parse('/'),
|
||||
const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -75,18 +75,18 @@ void main() {
|
||||
navigatorKey: GlobalKey<NavigatorState>(),
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first,
|
||||
subloc: '/',
|
||||
fullpath: '/',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
]);
|
||||
],
|
||||
Uri.parse('/'),
|
||||
<String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -117,18 +117,18 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first as GoRoute,
|
||||
subloc: '/',
|
||||
fullpath: '/',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
]);
|
||||
],
|
||||
Uri.parse('/'),
|
||||
<String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -172,28 +172,25 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first,
|
||||
subloc: '',
|
||||
fullpath: '',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>(''),
|
||||
),
|
||||
RouteMatch(
|
||||
route: config.routes.first.routes.first,
|
||||
subloc: '/details',
|
||||
fullpath: '/details',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/details'),
|
||||
),
|
||||
]);
|
||||
],
|
||||
Uri.parse('/details'),
|
||||
<String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -250,18 +247,18 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(<RouteMatch>[
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first.routes.first as GoRoute,
|
||||
subloc: '/a/details',
|
||||
fullpath: '/a/details',
|
||||
encodedParams: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
queryParametersAll: <String, List<String>>{},
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/a/details'),
|
||||
),
|
||||
]);
|
||||
],
|
||||
Uri.parse('/a/details'),
|
||||
<String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.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/misc/error_screen.dart';
|
||||
|
||||
@ -62,10 +63,6 @@ void main() {
|
||||
(WidgetTester tester) async {
|
||||
final GoRouter goRouter = await createGoRouter(tester);
|
||||
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
||||
expect(
|
||||
goRouter.routerDelegate.matches.matches[0].pageKey,
|
||||
null,
|
||||
);
|
||||
|
||||
goRouter.push('/a');
|
||||
await tester.pumpAndSettle();
|
||||
@ -113,8 +110,7 @@ void main() {
|
||||
});
|
||||
|
||||
group('replace', () {
|
||||
testWidgets(
|
||||
'It should replace the last match with the given one',
|
||||
testWidgets('It should replace the last match with the given one',
|
||||
(WidgetTester tester) async {
|
||||
final GoRouter goRouter = GoRouter(
|
||||
initialLocation: '/',
|
||||
@ -148,12 +144,15 @@ void main() {
|
||||
reason: 'The last match should have been removed',
|
||||
);
|
||||
expect(
|
||||
goRouter.routerDelegate.matches.last.fullpath,
|
||||
(goRouter.routerDelegate.matches.last as ImperativeRouteMatch)
|
||||
.matches
|
||||
.uri
|
||||
.toString(),
|
||||
'/page-1',
|
||||
reason: 'The new location should have been pushed',
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'It should return different pageKey when replace is called',
|
||||
(WidgetTester tester) async {
|
||||
@ -161,7 +160,7 @@ void main() {
|
||||
expect(goRouter.routerDelegate.matches.matches.length, 1);
|
||||
expect(
|
||||
goRouter.routerDelegate.matches.matches[0].pageKey,
|
||||
null,
|
||||
isNotNull,
|
||||
);
|
||||
|
||||
goRouter.push('/a');
|
||||
@ -228,13 +227,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
goRouter.routerDelegate.matches.last,
|
||||
isA<RouteMatch>()
|
||||
.having(
|
||||
(RouteMatch match) => match.fullpath,
|
||||
'match.fullpath',
|
||||
'/page-1',
|
||||
)
|
||||
.having(
|
||||
isA<RouteMatch>().having(
|
||||
(RouteMatch match) => (match.route as GoRoute).name,
|
||||
'match.route.name',
|
||||
'page1',
|
||||
|
@ -42,7 +42,7 @@ void main() {
|
||||
path: '/',
|
||||
builder: (_, __) {
|
||||
return Builder(builder: (BuildContext context) {
|
||||
return Text(GoRouterState.of(context).location);
|
||||
return Text('1 ${GoRouterState.of(context).location}');
|
||||
});
|
||||
},
|
||||
routes: <GoRoute>[
|
||||
@ -50,7 +50,7 @@ void main() {
|
||||
path: 'a',
|
||||
builder: (_, __) {
|
||||
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);
|
||||
router.go('/?p=123');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('/?p=123'), findsOneWidget);
|
||||
expect(find.text('1 /?p=123'), findsOneWidget);
|
||||
|
||||
router.go('/a');
|
||||
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.
|
||||
expect(find.text('/', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('1 /a', skipOffstage: false), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('registry retains GoRouterState for exiting route',
|
||||
|
@ -9,7 +9,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.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/matching.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'test_helpers.dart';
|
||||
@ -43,24 +45,24 @@ void main() {
|
||||
];
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.fullpath, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.uri.toString(), '/');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('If there is more than one route to match, use the first match',
|
||||
(WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(path: '/', builder: dummy),
|
||||
GoRoute(path: '/', builder: dummy),
|
||||
GoRoute(name: '1', path: '/', builder: dummy),
|
||||
GoRoute(name: '2', path: '/', builder: dummy),
|
||||
];
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.go('/');
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.fullpath, '/');
|
||||
expect((matches.first.route as GoRoute).name, '1');
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -72,6 +74,8 @@ void main() {
|
||||
|
||||
test('leading / on sub-route', () {
|
||||
expect(() {
|
||||
GoRouter(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: dummy,
|
||||
@ -81,12 +85,16 @@ void main() {
|
||||
builder: dummy,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}, throwsA(isAssertionError));
|
||||
});
|
||||
|
||||
test('trailing / on sub-route', () {
|
||||
expect(() {
|
||||
GoRouter(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: dummy,
|
||||
@ -96,6 +104,8 @@ void main() {
|
||||
builder: dummy,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}, throwsA(isAssertionError));
|
||||
});
|
||||
@ -328,44 +338,44 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
{
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.fullpath, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.uri.toString(), '/');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
}
|
||||
|
||||
router.go('/login');
|
||||
await tester.pumpAndSettle();
|
||||
{
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches.length, 2);
|
||||
expect(matches.first.subloc, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 2);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches[1].subloc, '/login');
|
||||
expect(matches.matches[1].subloc, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
}
|
||||
|
||||
router.go('/family/f2');
|
||||
await tester.pumpAndSettle();
|
||||
{
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches.length, 2);
|
||||
expect(matches.first.subloc, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 2);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
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);
|
||||
}
|
||||
|
||||
router.go('/family/f2/person/p1');
|
||||
await tester.pumpAndSettle();
|
||||
{
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches.length, 3);
|
||||
expect(matches.first.subloc, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 3);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
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(matches[2].subloc, '/family/f2/person/p1');
|
||||
expect(matches.matches[2].subloc, '/family/f2/person/p1');
|
||||
expect(find.byType(PersonScreen), findsOneWidget);
|
||||
}
|
||||
});
|
||||
@ -1134,14 +1144,12 @@ void main() {
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
final String loc = router
|
||||
.namedLocation('page1', params: <String, String>{'param1': param1});
|
||||
log.info('loc= $loc');
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
log.info('param1= ${matches.first.decodedParams['param1']}');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
expect(matches.first.decodedParams['param1'], param1);
|
||||
expect(matches.pathParameters['param1'], param1);
|
||||
});
|
||||
|
||||
testWidgets('preserve query param spaces and slashes',
|
||||
@ -1163,9 +1171,9 @@ void main() {
|
||||
queryParams: <String, String>{'param1': param1});
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
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';
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
|
||||
expect(router.location, loc);
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.matches, hasLength(1));
|
||||
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';
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
|
||||
expect(router.location, loc);
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.matches, hasLength(1));
|
||||
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);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
log.info('param1= ${matches.first.decodedParams['param1']}');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
expect(matches.first.decodedParams['param1'], param1);
|
||||
expect(matches.pathParameters['param1'], param1);
|
||||
});
|
||||
|
||||
testWidgets('preserve query param spaces and slashes',
|
||||
@ -1914,17 +1921,17 @@ void main() {
|
||||
router.go('/page1?param1=$param1');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
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)}';
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final List<RouteMatch> matches2 = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches2 = router.routerDelegate.matches;
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
expect(matches2[0].queryParams['param1'], param1);
|
||||
expect(matches2.uri.queryParameters['param1'], param1);
|
||||
});
|
||||
|
||||
test('error: duplicate path param', () {
|
||||
@ -1963,9 +1970,9 @@ void main() {
|
||||
tester,
|
||||
initialLocation: '/?id=0&id=1',
|
||||
);
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.fullpath, '/');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.fullpath, '/');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -1986,9 +1993,9 @@ void main() {
|
||||
|
||||
router.go('/0?id=1');
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.fullpath, '/:id');
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.fullpath, '/:id');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -2098,13 +2105,15 @@ void main() {
|
||||
|
||||
router.push(loc);
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
|
||||
expect(router.location, loc);
|
||||
expect(matches, hasLength(2));
|
||||
expect(matches.matches, hasLength(2));
|
||||
expect(find.byType(PersonScreen), findsOneWidget);
|
||||
expect(matches.last.decodedParams['fid'], fid);
|
||||
expect(matches.last.decodedParams['pid'], pid);
|
||||
final ImperativeRouteMatch imperativeRouteMatch =
|
||||
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',
|
||||
|
@ -14,47 +14,36 @@ void main() {
|
||||
path: '/users/:userId',
|
||||
builder: _builder,
|
||||
);
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: '/users/123',
|
||||
parentSubloc: '',
|
||||
fullpath: '/users/:userId',
|
||||
queryParams: <String, String>{},
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
queryParametersAll: <String, List<String>>{
|
||||
'bar': <String>['baz', 'biz'],
|
||||
},
|
||||
);
|
||||
if (match == null) {
|
||||
fail('Null match');
|
||||
}
|
||||
expect(match.route, route);
|
||||
expect(match.subloc, '/users/123');
|
||||
expect(match.fullpath, '/users/:userId');
|
||||
expect(match.encodedParams['userId'], '123');
|
||||
expect(match.queryParams['foo'], isNull);
|
||||
expect(match.queryParametersAll['bar'], <String>['baz', 'biz']);
|
||||
expect(pathParameters['userId'], '123');
|
||||
expect(match.extra, const _Extra('foo'));
|
||||
expect(match.error, isNull);
|
||||
expect(match.pageKey, isNull);
|
||||
expect(match.fullUriString, '/users/123?bar=baz&bar=biz');
|
||||
expect(match.pageKey, isNotNull);
|
||||
});
|
||||
|
||||
test('subloc', () {
|
||||
final GoRoute route = GoRoute(
|
||||
path: 'users/:userId',
|
||||
builder: _builder,
|
||||
);
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
fullpath: '/home/users/:userId',
|
||||
queryParams: <String, String>{
|
||||
'foo': 'bar',
|
||||
},
|
||||
queryParametersAll: <String, List<String>>{
|
||||
'foo': <String>['bar'],
|
||||
},
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
if (match == null) {
|
||||
@ -62,14 +51,12 @@ void main() {
|
||||
}
|
||||
expect(match.route, route);
|
||||
expect(match.subloc, '/home/users/123');
|
||||
expect(match.fullpath, '/home/users/:userId');
|
||||
expect(match.encodedParams['userId'], '123');
|
||||
expect(match.queryParams['foo'], 'bar');
|
||||
expect(pathParameters['userId'], '123');
|
||||
expect(match.extra, const _Extra('foo'));
|
||||
expect(match.error, isNull);
|
||||
expect(match.pageKey, isNull);
|
||||
expect(match.fullUriString, '/home/users/123?foo=bar');
|
||||
expect(match.pageKey, isNotNull);
|
||||
});
|
||||
|
||||
test('ShellRoute has a unique pageKey', () {
|
||||
final ShellRoute route = ShellRoute(
|
||||
builder: _shellBuilder,
|
||||
@ -80,17 +67,12 @@ void main() {
|
||||
),
|
||||
],
|
||||
);
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
fullpath: '/home/users/:userId',
|
||||
queryParams: <String, String>{
|
||||
'foo': 'bar',
|
||||
},
|
||||
queryParametersAll: <String, List<String>>{
|
||||
'foo': <String>['bar'],
|
||||
},
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
if (match == null) {
|
||||
@ -98,6 +80,61 @@ void main() {
|
||||
}
|
||||
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);
|
||||
List<RouteMatch> matches = matchesObj.matches;
|
||||
expect(matches.length, 1);
|
||||
expect(matches[0].queryParams.isEmpty, isTrue);
|
||||
expect(matchesObj.uri.toString(), '/');
|
||||
expect(matches[0].extra, isNull);
|
||||
expect(matches[0].fullUriString, '/');
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].route, routes[0]);
|
||||
|
||||
@ -67,17 +66,12 @@ void main() {
|
||||
RouteInformation(location: '/abc?def=ghi', state: extra), context);
|
||||
matches = matchesObj.matches;
|
||||
expect(matches.length, 2);
|
||||
expect(matches[0].queryParams.length, 1);
|
||||
expect(matches[0].queryParams['def'], 'ghi');
|
||||
expect(matchesObj.uri.toString(), '/abc?def=ghi');
|
||||
expect(matches[0].extra, extra);
|
||||
expect(matches[0].fullUriString, '/?def=ghi');
|
||||
expect(matches[0].subloc, '/');
|
||||
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].fullUriString, '/abc?def=ghi');
|
||||
expect(matches[1].subloc, '/abc');
|
||||
expect(matches[1].route, routes[0].routes[0]);
|
||||
});
|
||||
@ -195,9 +189,8 @@ void main() {
|
||||
const RouteInformation(location: '/def'), context);
|
||||
final List<RouteMatch> matches = matchesObj.matches;
|
||||
expect(matches.length, 1);
|
||||
expect(matches[0].queryParams.isEmpty, isTrue);
|
||||
expect(matchesObj.uri.toString(), '/def');
|
||||
expect(matches[0].extra, isNull);
|
||||
expect(matches[0].fullUriString, '/def');
|
||||
expect(matches[0].subloc, '/def');
|
||||
expect(matches[0].error!.toString(),
|
||||
'Exception: no routes for location: /def');
|
||||
@ -231,18 +224,15 @@ void main() {
|
||||
final List<RouteMatch> matches = matchesObj.matches;
|
||||
|
||||
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].fullUriString, '/');
|
||||
expect(matches[0].subloc, '/');
|
||||
|
||||
expect(matches[1].queryParams.isEmpty, isTrue);
|
||||
expect(matches[1].extra, isNull);
|
||||
expect(matches[1].fullUriString, '/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(
|
||||
@ -279,10 +269,9 @@ void main() {
|
||||
final List<RouteMatch> matches = matchesObj.matches;
|
||||
|
||||
expect(matches.length, 2);
|
||||
expect(matches[0].fullUriString, '/');
|
||||
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||
expect(matches[0].subloc, '/');
|
||||
|
||||
expect(matches[1].fullUriString, '/123/family/345');
|
||||
expect(matches[1].subloc, '/123/family/345');
|
||||
});
|
||||
|
||||
@ -320,10 +309,9 @@ void main() {
|
||||
final List<RouteMatch> matches = matchesObj.matches;
|
||||
|
||||
expect(matches.length, 2);
|
||||
expect(matches[0].fullUriString, '/');
|
||||
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||
expect(matches[0].subloc, '/');
|
||||
|
||||
expect(matches[1].fullUriString, '/123/family/345');
|
||||
expect(matches[1].subloc, '/123/family/345');
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user