mirror of
https://github.com/flutter/packages.git
synced 2025-07-02 16:39:13 +08:00
[go_router] Cleans up route match API and introduces dart fix (#3819)
Clean up API around RouteMatch/RouteMatchList/GoRouterState, This is a breaking change that renamed some of the GoRouterState property to have a more descriptive name as flutter style guide suggested https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-abbreviations also introducing dart fix to help with migration
This commit is contained in:
@ -1,3 +1,19 @@
|
||||
## 7.0.0
|
||||
|
||||
- **BREAKING CHANGE**:
|
||||
- For the below changes, run `dart fix --apply` to automatically migrate your code.
|
||||
- `GoRouteState.subloc` has been renamed to `GoRouteState.matchedLocation`.
|
||||
- `GoRouteState.params` has been renamed to `GoRouteState.pathParameters`.
|
||||
- `GoRouteState.fullpath` has been renamed to `GoRouteState.fullPath`.
|
||||
- `GoRouteState.queryParams` has been renamed to `GoRouteState.queryParameters`.
|
||||
- `params` and `queryParams` in `GoRouteState.namedLocation` have been renamed to `pathParameters` and `queryParameters`.
|
||||
- `params` and `queryParams` in `GoRouter`'s `namedLocation`, `pushNamed`, `pushReplacementNamed`
|
||||
`replaceNamed` have been renamed to `pathParameters` and `queryParameters`.
|
||||
- For the below changes, please follow the [migration guide](https://docs.google.com/document/d/10Xbpifbs4E-zh6YE5akIO8raJq_m3FIXs6nUGdOspOg).
|
||||
- `params` and `queryParams` in `BuildContext`'s `namedLocation`, `pushNamed`, `pushReplacementNamed`
|
||||
`replaceNamed` have been renamed to `pathParameters` and `queryParameters`.
|
||||
- Cleans up API and makes RouteMatchList immutable.
|
||||
|
||||
## 6.5.9
|
||||
|
||||
- Removes navigator keys from `GoRouteData` and `ShellRouteData`.
|
||||
|
@ -37,6 +37,7 @@ See the API documentation for details on the following topics:
|
||||
- [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html)
|
||||
|
||||
## Migration guides
|
||||
- [Migrating to 7.0.0](https://docs.google.com/document/d/10Xbpifbs4E-zh6YE5akIO8raJq_m3FIXs6nUGdOspOg).
|
||||
- [Migrating to 6.0.0](https://flutter.dev/go/go-router-v6-breaking-changes)
|
||||
- [Migrating to 5.1.2](https://flutter.dev/go/go-router-v5-1-2-breaking-changes)
|
||||
- [Migrating to 5.0](https://flutter.dev/go/go-router-v5-breaking-changes)
|
||||
|
5
packages/go_router/analysis_options.yaml
Normal file
5
packages/go_router/analysis_options.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
include: ../../analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "test_fixes/**"
|
@ -43,7 +43,7 @@ the builder callback:
|
||||
```dart
|
||||
GoRoute(
|
||||
path: '/users/:userId',
|
||||
builder: (context, state) => const UserScreen(id: state.params['userId']),
|
||||
builder: (context, state) => const UserScreen(id: state.pathParameters['userId']),
|
||||
),
|
||||
```
|
||||
|
||||
@ -55,7 +55,7 @@ after the `?`), use [GoRouterState][]. For example, a URL path such as
|
||||
```dart
|
||||
GoRoute(
|
||||
path: '/users',
|
||||
builder: (context, state) => const UsersScreen(filter: state.queryParams['filter']),
|
||||
builder: (context, state) => const UsersScreen(filter: state.queryParameters['filter']),
|
||||
),
|
||||
```
|
||||
|
||||
|
@ -14,7 +14,7 @@ To navigate to a route using its name, call [`goNamed`](https://pub.dev/document
|
||||
```dart
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.goNamed('song', params: {'songId': 123});
|
||||
context.goNamed('song', pathParameters: {'songId': 123});
|
||||
},
|
||||
child: const Text('Go to song 2'),
|
||||
),
|
||||
@ -25,7 +25,7 @@ Alternatively, you can look up the location for a name using `namedLocation`:
|
||||
```dart
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final String location = context.namedLocation('song', params: {'songId': 123});
|
||||
final String location = context.namedLocation('song', pathParameters: {'songId': 123});
|
||||
context.go(location);
|
||||
},
|
||||
child: const Text('Go to song 2'),
|
||||
|
@ -55,7 +55,7 @@ class App extends StatelessWidget {
|
||||
// cause go_router to reparse current route if StreamAuth has new sign-in
|
||||
// information.
|
||||
final bool loggedIn = await StreamAuthScope.of(context).isSignedIn();
|
||||
final bool loggingIn = state.subloc == '/login';
|
||||
final bool loggingIn = state.matchedLocation == '/login';
|
||||
if (!loggedIn) {
|
||||
return '/login';
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class Bookstore extends StatelessWidget {
|
||||
GoRoute(
|
||||
path: '/book/:bookId',
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
'/books/all/${state.params['bookId']}',
|
||||
'/books/all/${state.pathParameters['bookId']}',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/books/:kind(new|all|popular)',
|
||||
@ -72,14 +72,14 @@ class Bookstore extends StatelessWidget {
|
||||
key: _scaffoldKey,
|
||||
child: BookstoreScaffold(
|
||||
selectedTab: ScaffoldTab.books,
|
||||
child: BooksScreen(state.params['kind']!),
|
||||
child: BooksScreen(state.pathParameters['kind']!),
|
||||
),
|
||||
),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: ':bookId',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final String bookId = state.params['bookId']!;
|
||||
final String bookId = state.pathParameters['bookId']!;
|
||||
final Book? selectedBook = libraryInstance.allBooks
|
||||
.firstWhereOrNull((Book b) => b.id.toString() == bookId);
|
||||
|
||||
@ -91,7 +91,7 @@ class Bookstore extends StatelessWidget {
|
||||
GoRoute(
|
||||
path: '/author/:authorId',
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
'/authors/${state.params['authorId']}',
|
||||
'/authors/${state.pathParameters['authorId']}',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/authors',
|
||||
@ -107,7 +107,7 @@ class Bookstore extends StatelessWidget {
|
||||
GoRoute(
|
||||
path: ':authorId',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final int authorId = int.parse(state.params['authorId']!);
|
||||
final int authorId = int.parse(state.pathParameters['authorId']!);
|
||||
final Author? selectedAuthor = libraryInstance.allAuthors
|
||||
.firstWhereOrNull((Author a) => a.id == authorId);
|
||||
|
||||
@ -135,7 +135,7 @@ class Bookstore extends StatelessWidget {
|
||||
|
||||
String? _guard(BuildContext context, GoRouterState state) {
|
||||
final bool signedIn = _auth.signedIn;
|
||||
final bool signingIn = state.subloc == '/signin';
|
||||
final bool signingIn = state.matchedLocation == '/signin';
|
||||
|
||||
// Go to /signin if the user is not signed in
|
||||
if (!signedIn && !signingIn) {
|
||||
|
@ -84,14 +84,15 @@ class App extends StatelessWidget {
|
||||
name: 'family',
|
||||
path: 'family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
FamilyScreen(fid: state.params['fid']!),
|
||||
FamilyScreen(fid: state.pathParameters['fid']!),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
name: 'person',
|
||||
path: 'person/:pid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return PersonScreen(
|
||||
fid: state.params['fid']!, pid: state.params['pid']!);
|
||||
fid: state.pathParameters['fid']!,
|
||||
pid: state.pathParameters['pid']!);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -119,7 +120,7 @@ class HomeScreen extends StatelessWidget {
|
||||
ListTile(
|
||||
title: Text(entry.value.name),
|
||||
onTap: () => context.go(context.namedLocation('family',
|
||||
params: <String, String>{'fid': entry.key})),
|
||||
pathParameters: <String, String>{'fid': entry.key})),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -147,8 +148,8 @@ class FamilyScreen extends StatelessWidget {
|
||||
title: Text(entry.value.name),
|
||||
onTap: () => context.go(context.namedLocation(
|
||||
'person',
|
||||
params: <String, String>{'fid': fid, 'pid': entry.key},
|
||||
queryParams: <String, String>{'qid': 'quid'},
|
||||
pathParameters: <String, String>{'fid': fid, 'pid': entry.key},
|
||||
queryParameters: <String, String>{'qid': 'quid'},
|
||||
)),
|
||||
),
|
||||
],
|
||||
|
@ -108,8 +108,8 @@ class Page1Screen extends StatelessWidget {
|
||||
ElevatedButton(
|
||||
onPressed: () => context.goNamed(
|
||||
'page2',
|
||||
params: <String, String>{'p1': 'pv1'},
|
||||
queryParams: <String, String>{'q1': 'qv1'},
|
||||
pathParameters: <String, String>{'p1': 'pv1'},
|
||||
queryParameters: <String, String>{'q1': 'qv1'},
|
||||
),
|
||||
child: const Text('Go to page 2'),
|
||||
),
|
||||
@ -134,7 +134,7 @@ class Page2Screen extends StatelessWidget {
|
||||
ElevatedButton(
|
||||
onPressed: () => context.goNamed(
|
||||
'page3',
|
||||
params: <String, String>{'p1': 'pv2'},
|
||||
pathParameters: <String, String>{'p1': 'pv2'},
|
||||
),
|
||||
child: const Text('Go to page 3'),
|
||||
),
|
||||
|
@ -32,7 +32,7 @@ class App extends StatelessWidget {
|
||||
path: '/page2',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
Page2ScreenWithPush(
|
||||
int.parse(state.queryParams['push-count']!),
|
||||
int.parse(state.queryParameters['push-count']!),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -9,9 +9,9 @@ import 'package:go_router/go_router.dart';
|
||||
//
|
||||
// The route segments that start with ':' are treated as path parameters when
|
||||
// defining GoRoute[s]. The parameter values can be accessed through
|
||||
// GoRouterState.params.
|
||||
// GoRouterState.pathParameters.
|
||||
//
|
||||
// The query parameters are automatically stored in GoRouterState.queryParams.
|
||||
// The query parameters are automatically stored in GoRouterState.queryParameters.
|
||||
|
||||
/// Family data class.
|
||||
class Family {
|
||||
@ -84,8 +84,8 @@ class App extends StatelessWidget {
|
||||
path: 'family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return FamilyScreen(
|
||||
fid: state.params['fid']!,
|
||||
asc: state.queryParams['sort'] == 'asc',
|
||||
fid: state.pathParameters['fid']!,
|
||||
asc: state.queryParameters['sort'] == 'asc',
|
||||
);
|
||||
}),
|
||||
],
|
||||
@ -149,7 +149,8 @@ class FamilyScreen extends StatelessWidget {
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => context.goNamed('family',
|
||||
params: <String, String>{'fid': fid}, queryParams: newQueries),
|
||||
pathParameters: <String, String>{'fid': fid},
|
||||
queryParameters: newQueries),
|
||||
tooltip: 'sort ascending or descending',
|
||||
icon: const Icon(Icons.sort),
|
||||
)
|
||||
|
@ -75,7 +75,7 @@ class App extends StatelessWidget {
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
// if the user is not logged in, they need to login
|
||||
final bool loggedIn = _loginInfo.loggedIn;
|
||||
final bool loggingIn = state.subloc == '/login';
|
||||
final bool loggingIn = state.matchedLocation == '/login';
|
||||
if (!loggedIn) {
|
||||
return '/login';
|
||||
}
|
||||
|
151
packages/go_router/lib/fix_data.yaml
Normal file
151
packages/go_router/lib/fix_data.yaml
Normal file
@ -0,0 +1,151 @@
|
||||
# Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# For details regarding the *Flutter Fix* feature, see
|
||||
# https://flutter.dev/docs/development/tools/flutter-fix
|
||||
|
||||
# Please add new fixes to the top of the file, separated by one blank line
|
||||
# from other fixes. In a comment, include a link to the PR where the change
|
||||
# requiring the fix was made.
|
||||
|
||||
# Every fix must be tested. See the flutter/packages/flutter/test_fixes/README.md
|
||||
# file for instructions on testing these data driven fixes.
|
||||
|
||||
# For documentation about this file format, see
|
||||
# https://dart.dev/go/data-driven-fixes
|
||||
|
||||
version: 1
|
||||
transforms:
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouter.replaceNamed' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'replaceNamed'
|
||||
inClass: 'GoRouter'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouter.pushReplacementNamed' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'pushReplacementNamed'
|
||||
inClass: 'GoRouter'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouter.pushNamed' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'pushNamed'
|
||||
inClass: 'GoRouter'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouter.goNamed' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'goNamed'
|
||||
inClass: 'GoRouter'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouter.namedLocation' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'namedLocation'
|
||||
inClass: 'GoRouter'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'params' and 'queryParams' in 'GoRouterState.namedLocation' with `pathParameters` and `queryParameters`"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
method: 'namedLocation'
|
||||
inClass: 'GoRouterState'
|
||||
changes:
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'params'
|
||||
newName: 'pathParameters'
|
||||
- kind: 'renameParameter'
|
||||
oldName: 'queryParams'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'GoRouterState.queryParams' with 'GoRouterState.queryParameters'"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
field: 'queryParams'
|
||||
inClass: 'GoRouterState'
|
||||
changes:
|
||||
- kind: 'rename'
|
||||
newName: 'queryParameters'
|
||||
|
||||
- title: "Replaces 'GoRouterState.fullpath' with 'GoRouterState.fullPath'"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
field: 'fullpath'
|
||||
inClass: 'GoRouterState'
|
||||
changes:
|
||||
- kind: 'rename'
|
||||
newName: 'fullPath'
|
||||
|
||||
- title: "Replaces 'GoRouterState.params' with 'GoRouterState.pathParameters'"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
field: 'params'
|
||||
inClass: 'GoRouterState'
|
||||
changes:
|
||||
- kind: 'rename'
|
||||
newName: 'pathParameters'
|
||||
|
||||
- title: "Replaces 'GoRouterState.subloc' with 'GoRouterState.matchedLocation'"
|
||||
date: 2023-04-24
|
||||
bulkApply: true
|
||||
element:
|
||||
uris: [ 'go_router.dart' ]
|
||||
field: 'subloc'
|
||||
inClass: 'GoRouterState'
|
||||
changes:
|
||||
- kind: 'rename'
|
||||
newName: 'matchedLocation'
|
@ -6,7 +6,6 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'logging.dart';
|
||||
import 'match.dart';
|
||||
import 'matching.dart';
|
||||
@ -254,7 +253,7 @@ class RouteBuilder {
|
||||
}
|
||||
|
||||
/// Helper method that builds a [GoRouterState] object for the given [match]
|
||||
/// and [params].
|
||||
/// and [pathParameters].
|
||||
@visibleForTesting
|
||||
GoRouterState buildState(RouteMatchList matchList, RouteMatch match) {
|
||||
final RouteBase route = match.route;
|
||||
@ -269,13 +268,14 @@ class RouteBuilder {
|
||||
return GoRouterState(
|
||||
configuration,
|
||||
location: effectiveMatchList.uri.toString(),
|
||||
subloc: match.subloc,
|
||||
matchedLocation: match.matchedLocation,
|
||||
name: name,
|
||||
path: path,
|
||||
fullpath: effectiveMatchList.fullpath,
|
||||
params: Map<String, String>.from(effectiveMatchList.pathParameters),
|
||||
fullPath: effectiveMatchList.fullPath,
|
||||
pathParameters:
|
||||
Map<String, String>.from(effectiveMatchList.pathParameters),
|
||||
error: match.error,
|
||||
queryParams: effectiveMatchList.uri.queryParameters,
|
||||
queryParameters: effectiveMatchList.uri.queryParameters,
|
||||
queryParametersAll: effectiveMatchList.uri.queryParametersAll,
|
||||
extra: match.extra,
|
||||
pageKey: match.pageKey,
|
||||
@ -397,7 +397,10 @@ class RouteBuilder {
|
||||
return _pageBuilderForAppType!(
|
||||
key: state.pageKey,
|
||||
name: state.name ?? state.path,
|
||||
arguments: <String, String>{...state.params, ...state.queryParams},
|
||||
arguments: <String, String>{
|
||||
...state.pathParameters,
|
||||
...state.queryParameters
|
||||
},
|
||||
restorationId: state.pageKey.value,
|
||||
child: child,
|
||||
);
|
||||
@ -444,9 +447,9 @@ class RouteBuilder {
|
||||
final GoRouterState state = GoRouterState(
|
||||
configuration,
|
||||
location: uri.toString(),
|
||||
subloc: uri.path,
|
||||
matchedLocation: uri.path,
|
||||
name: null,
|
||||
queryParams: uri.queryParameters,
|
||||
queryParameters: uri.queryParameters,
|
||||
queryParametersAll: uri.queryParametersAll,
|
||||
error: Exception(error),
|
||||
pageKey: const ValueKey<String>('error'),
|
||||
|
@ -97,7 +97,7 @@ class RouteConfiguration {
|
||||
if (route is! GoRoute) {
|
||||
continue;
|
||||
}
|
||||
for (final String pathParam in route.pathParams) {
|
||||
for (final String pathParam in route.pathParameters) {
|
||||
if (usedPathParams.containsKey(pathParam)) {
|
||||
final bool sameRoute = usedPathParams[pathParam] == route;
|
||||
throw GoError(
|
||||
@ -106,7 +106,7 @@ class RouteConfiguration {
|
||||
usedPathParams[pathParam] = route;
|
||||
}
|
||||
_debugVerifyNoDuplicatePathParameter(route.routes, usedPathParams);
|
||||
route.pathParams.forEach(usedPathParams.remove);
|
||||
route.pathParameters.forEach(usedPathParams.remove);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -128,14 +128,14 @@ class RouteConfiguration {
|
||||
/// Looks up the url location by a [GoRoute]'s name.
|
||||
String namedLocation(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
}) {
|
||||
assert(() {
|
||||
log.info('getting location for name: '
|
||||
'"$name"'
|
||||
'${params.isEmpty ? '' : ', params: $params'}'
|
||||
'${queryParams.isEmpty ? '' : ', queryParams: $queryParams'}');
|
||||
'${pathParameters.isEmpty ? '' : ', pathParameters: $pathParameters'}'
|
||||
'${queryParameters.isEmpty ? '' : ', queryParameters: $queryParameters'}');
|
||||
return true;
|
||||
}());
|
||||
final String keyName = name.toLowerCase();
|
||||
@ -146,24 +146,24 @@ class RouteConfiguration {
|
||||
final List<String> paramNames = <String>[];
|
||||
patternToRegExp(path, paramNames);
|
||||
for (final String paramName in paramNames) {
|
||||
assert(params.containsKey(paramName),
|
||||
assert(pathParameters.containsKey(paramName),
|
||||
'missing param "$paramName" for $path');
|
||||
}
|
||||
|
||||
// Check that there are no extra params
|
||||
for (final String key in params.keys) {
|
||||
for (final String key in pathParameters.keys) {
|
||||
assert(paramNames.contains(key), 'unknown param "$key" for $path');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
final Map<String, String> encodedParams = <String, String>{
|
||||
for (final MapEntry<String, String> param in params.entries)
|
||||
for (final MapEntry<String, String> param in pathParameters.entries)
|
||||
param.key: Uri.encodeComponent(param.value)
|
||||
};
|
||||
final String location = patternToPath(path, encodedParams);
|
||||
return Uri(
|
||||
path: location,
|
||||
queryParameters: queryParams.isEmpty ? null : queryParams)
|
||||
queryParameters: queryParameters.isEmpty ? null : queryParameters)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@ -196,9 +196,9 @@ class RouteConfiguration {
|
||||
int depth, StringBuffer sb) {
|
||||
for (final RouteBase route in routes) {
|
||||
if (route is GoRoute) {
|
||||
final String fullpath = concatenatePaths(parentFullpath, route.path);
|
||||
sb.writeln(' => ${''.padLeft(depth * 2)}$fullpath');
|
||||
_debugFullPathsFor(route.routes, fullpath, depth + 1, sb);
|
||||
final String fullPath = concatenatePaths(parentFullpath, route.path);
|
||||
sb.writeln(' => ${''.padLeft(depth * 2)}$fullPath');
|
||||
_debugFullPathsFor(route.routes, fullPath, depth + 1, sb);
|
||||
} else if (route is ShellRoute) {
|
||||
_debugFullPathsFor(route.routes, parentFullpath, depth, sb);
|
||||
}
|
||||
|
@ -86,15 +86,19 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
RouteMatchList matches, ValueKey<String> pageKey) async {
|
||||
final ImperativeRouteMatch<T> newPageKeyMatch = ImperativeRouteMatch<T>(
|
||||
route: matches.last.route,
|
||||
subloc: matches.last.subloc,
|
||||
matchedLocation: matches.last.matchedLocation,
|
||||
extra: matches.last.extra,
|
||||
error: matches.last.error,
|
||||
pageKey: pageKey,
|
||||
matches: matches,
|
||||
);
|
||||
|
||||
_matchList.push(newPageKeyMatch);
|
||||
return newPageKeyMatch._future;
|
||||
_matchList = _matchList.push(newPageKeyMatch);
|
||||
return newPageKeyMatch.future;
|
||||
}
|
||||
|
||||
void _remove(RouteMatch match) {
|
||||
_matchList = _matchList.remove(match);
|
||||
}
|
||||
|
||||
/// Pushes the given location onto the page stack.
|
||||
@ -108,7 +112,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
Future<T?> push<T extends Object?>(RouteMatchList matches) async {
|
||||
assert(matches.last.route is! ShellRoute);
|
||||
|
||||
final ValueKey<String> pageKey = _getNewKeyForPath(matches.fullpath);
|
||||
final ValueKey<String> pageKey = _getNewKeyForPath(matches.fullPath);
|
||||
final Future<T?> future = _push(matches, pageKey);
|
||||
notifyListeners();
|
||||
return future;
|
||||
@ -155,7 +159,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
if (match is ImperativeRouteMatch) {
|
||||
match.complete(result);
|
||||
}
|
||||
_matchList.remove(match!);
|
||||
_remove(match!);
|
||||
notifyListeners();
|
||||
assert(() {
|
||||
_debugAssertMatchListNotEmpty();
|
||||
@ -175,7 +179,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
/// state and not run any page animation.
|
||||
void pushReplacement(RouteMatchList matches) {
|
||||
assert(matches.last.route is! ShellRoute);
|
||||
_matchList.remove(_matchList.last);
|
||||
_remove(_matchList.last);
|
||||
push(matches); // [push] will notify the listeners.
|
||||
}
|
||||
|
||||
@ -193,7 +197,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
assert(matches.last.route is! ShellRoute);
|
||||
final RouteMatch routeMatch = _matchList.last;
|
||||
final ValueKey<String> pageKey = routeMatch.pageKey;
|
||||
_matchList.remove(routeMatch);
|
||||
_remove(routeMatch);
|
||||
_push(matches, pageKey);
|
||||
notifyListeners();
|
||||
}
|
||||
@ -309,32 +313,3 @@ class _NavigatorStateIterator extends Iterator<NavigatorState> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// The route match that represent route pushed through [GoRouter.push].
|
||||
class ImperativeRouteMatch<T> extends RouteMatch {
|
||||
/// Constructor for [ImperativeRouteMatch].
|
||||
ImperativeRouteMatch({
|
||||
required super.route,
|
||||
required super.subloc,
|
||||
required super.extra,
|
||||
required super.error,
|
||||
required super.pageKey,
|
||||
required this.matches,
|
||||
}) : _completer = Completer<T?>();
|
||||
|
||||
/// The matches that produces this route match.
|
||||
final RouteMatchList matches;
|
||||
|
||||
/// The completer for the future returned by [GoRouter.push].
|
||||
final Completer<T?> _completer;
|
||||
|
||||
/// Called when the corresponding [Route] associated with this route match is
|
||||
/// completed.
|
||||
void complete([dynamic value]) {
|
||||
_completer.complete(value as T?);
|
||||
}
|
||||
|
||||
/// The future of the [RouteMatch] completer.
|
||||
/// When the future completes, this will return the value passed to [complete].
|
||||
Future<T?> get _future => _completer.future;
|
||||
}
|
||||
|
@ -2,35 +2,44 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'matching.dart';
|
||||
import 'path_utils.dart';
|
||||
import 'route.dart';
|
||||
|
||||
/// An instance of a GoRoute plus information about the current location.
|
||||
/// An matched result by matching a [RouteBase] against a location.
|
||||
///
|
||||
/// This is typically created by calling [RouteMatch.match].
|
||||
@immutable
|
||||
class RouteMatch {
|
||||
/// Constructor for [RouteMatch].
|
||||
RouteMatch({
|
||||
const RouteMatch({
|
||||
required this.route,
|
||||
required this.subloc,
|
||||
required this.matchedLocation,
|
||||
required this.extra,
|
||||
required this.error,
|
||||
required this.pageKey,
|
||||
});
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
/// Generate a [RouteMatch] object by matching the `route` with
|
||||
/// `remainingLocation`.
|
||||
///
|
||||
/// The extracted path parameters, as the result of the matching, are stored
|
||||
/// into `pathParameters`.
|
||||
static RouteMatch? match({
|
||||
required RouteBase route,
|
||||
required String restLoc, // e.g. person/p1
|
||||
required String parentSubloc, // e.g. /family/f2
|
||||
required String remainingLocation, // e.g. person/p1
|
||||
required String matchedLocation, // e.g. /family/f2
|
||||
required Map<String, String> pathParameters,
|
||||
required Object? extra,
|
||||
}) {
|
||||
if (route is ShellRoute) {
|
||||
return RouteMatch(
|
||||
route: route,
|
||||
subloc: restLoc,
|
||||
matchedLocation: remainingLocation,
|
||||
extra: extra,
|
||||
error: null,
|
||||
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||
@ -38,7 +47,7 @@ class RouteMatch {
|
||||
} else if (route is GoRoute) {
|
||||
assert(!route.path.contains('//'));
|
||||
|
||||
final RegExpMatch? match = route.matchPatternAsPrefix(restLoc);
|
||||
final RegExpMatch? match = route.matchPatternAsPrefix(remainingLocation);
|
||||
if (match == null) {
|
||||
return null;
|
||||
}
|
||||
@ -48,23 +57,31 @@ class RouteMatch {
|
||||
pathParameters[param.key] = Uri.decodeComponent(param.value);
|
||||
}
|
||||
final String pathLoc = patternToPath(route.path, encodedParams);
|
||||
final String subloc = concatenatePaths(parentSubloc, pathLoc);
|
||||
final String newMatchedLocation =
|
||||
concatenatePaths(matchedLocation, pathLoc);
|
||||
return RouteMatch(
|
||||
route: route,
|
||||
subloc: subloc,
|
||||
matchedLocation: newMatchedLocation,
|
||||
extra: extra,
|
||||
error: null,
|
||||
pageKey: ValueKey<String>(route.hashCode.toString()),
|
||||
);
|
||||
}
|
||||
throw MatcherError('Unexpected route type: $route', restLoc);
|
||||
throw MatcherError('Unexpected route type: $route', remainingLocation);
|
||||
}
|
||||
|
||||
/// The matched route.
|
||||
final RouteBase route;
|
||||
|
||||
/// The matched location.
|
||||
final String subloc; // e.g. /family/f2
|
||||
/// The location string that matches the [route].
|
||||
///
|
||||
/// for example:
|
||||
///
|
||||
/// uri = '/family/f2/person/p2'
|
||||
/// route = GoRoute('/family/:id)
|
||||
///
|
||||
/// matchedLocation = '/family/f2'
|
||||
final String matchedLocation;
|
||||
|
||||
/// An extra object to pass along with the navigation.
|
||||
final Object? extra;
|
||||
@ -74,4 +91,59 @@ class RouteMatch {
|
||||
|
||||
/// Value key of type string, to hold a unique reference to a page.
|
||||
final ValueKey<String> pageKey;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is RouteMatch &&
|
||||
route == other.route &&
|
||||
matchedLocation == other.matchedLocation &&
|
||||
extra == other.extra &&
|
||||
pageKey == other.pageKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(route, matchedLocation, extra, pageKey);
|
||||
}
|
||||
|
||||
/// The route match that represent route pushed through [GoRouter.push].
|
||||
class ImperativeRouteMatch<T> extends RouteMatch {
|
||||
/// Constructor for [ImperativeRouteMatch].
|
||||
ImperativeRouteMatch({
|
||||
required super.route,
|
||||
required super.matchedLocation,
|
||||
required super.extra,
|
||||
required super.error,
|
||||
required super.pageKey,
|
||||
required this.matches,
|
||||
}) : _completer = Completer<T?>();
|
||||
|
||||
/// The matches that produces this route match.
|
||||
final RouteMatchList matches;
|
||||
|
||||
/// The completer for the future returned by [GoRouter.push].
|
||||
final Completer<T?> _completer;
|
||||
|
||||
/// Called when the corresponding [Route] associated with this route match is
|
||||
/// completed.
|
||||
void complete([dynamic value]) {
|
||||
_completer.complete(value as T?);
|
||||
}
|
||||
|
||||
/// The future of the [RouteMatch] completer.
|
||||
/// When the future completes, this will return the value passed to [complete].
|
||||
Future<T?> get future => _completer.future;
|
||||
|
||||
// An ImperativeRouteMatch has its own life cycle due the the _completer.
|
||||
// comparing _completer between instances would be the same thing as
|
||||
// comparing object reference.
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => identityHashCode(this);
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
// 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/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'match.dart';
|
||||
import 'path_utils.dart';
|
||||
|
||||
@ -25,16 +25,17 @@ class RouteMatcher {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final List<RouteMatch> matches =
|
||||
_getLocRouteMatches(uri, extra, pathParameters);
|
||||
return RouteMatchList(matches, uri, pathParameters);
|
||||
return RouteMatchList(
|
||||
matches: matches, uri: uri, pathParameters: pathParameters);
|
||||
}
|
||||
|
||||
List<RouteMatch> _getLocRouteMatches(
|
||||
Uri uri, Object? extra, Map<String, String> pathParameters) {
|
||||
final List<RouteMatch>? result = _getLocRouteRecursively(
|
||||
loc: uri.path,
|
||||
restLoc: uri.path,
|
||||
location: uri.path,
|
||||
remainingLocation: uri.path,
|
||||
routes: configuration.routes,
|
||||
parentSubloc: '',
|
||||
matchedLocation: '',
|
||||
pathParameters: pathParameters,
|
||||
extra: extra,
|
||||
);
|
||||
@ -50,19 +51,50 @@ class RouteMatcher {
|
||||
/// The list of [RouteMatch] objects.
|
||||
///
|
||||
/// This corresponds to the GoRouter's history.
|
||||
@immutable
|
||||
class RouteMatchList {
|
||||
/// RouteMatchList constructor.
|
||||
RouteMatchList(List<RouteMatch> matches, this._uri, this.pathParameters)
|
||||
: _matches = matches,
|
||||
fullpath = _generateFullPath(matches);
|
||||
RouteMatchList({
|
||||
required this.matches,
|
||||
required this.uri,
|
||||
required this.pathParameters,
|
||||
}) : fullPath = _generateFullPath(matches);
|
||||
|
||||
/// Constructs an empty matches object.
|
||||
static RouteMatchList empty =
|
||||
RouteMatchList(<RouteMatch>[], Uri.parse(''), const <String, String>{});
|
||||
static RouteMatchList empty = RouteMatchList(
|
||||
matches: const <RouteMatch>[],
|
||||
uri: Uri(),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
/// The route matches.
|
||||
final List<RouteMatch> matches;
|
||||
|
||||
/// Parameters for the matched route, URI-encoded.
|
||||
///
|
||||
/// The parameters only reflects [RouteMatch]s that are not
|
||||
/// [ImperativeRouteMatch].
|
||||
final Map<String, String> pathParameters;
|
||||
|
||||
/// The uri of the current match.
|
||||
///
|
||||
/// This uri only reflects [RouteMatch]s that are not [ImperativeRouteMatch].
|
||||
final Uri uri;
|
||||
|
||||
/// the full path pattern that matches the uri.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// '/family/:fid/person/:pid'
|
||||
/// ```
|
||||
final String fullPath;
|
||||
|
||||
/// Generates the full path (ex: `'/family/:fid/person/:pid'`) of a list of
|
||||
/// [RouteMatch].
|
||||
///
|
||||
/// This method ignores [ImperativeRouteMatch]s in the `matches`, as they
|
||||
/// don't contribute to the path.
|
||||
///
|
||||
/// This methods considers that [matches]'s elements verify the go route
|
||||
/// structure given to `GoRouter`. For example, if the routes structure is
|
||||
///
|
||||
@ -90,7 +122,8 @@ class RouteMatchList {
|
||||
static String _generateFullPath(Iterable<RouteMatch> matches) {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
bool addsSlash = false;
|
||||
for (final RouteMatch match in matches) {
|
||||
for (final RouteMatch match in matches
|
||||
.where((RouteMatch match) => match is! ImperativeRouteMatch)) {
|
||||
final RouteBase route = match.route;
|
||||
if (route is GoRoute) {
|
||||
if (addsSlash) {
|
||||
@ -103,66 +136,61 @@ class RouteMatchList {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
final List<RouteMatch> _matches;
|
||||
|
||||
/// the full path pattern that matches the uri.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// '/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.
|
||||
Uri get uri => _uri;
|
||||
Uri _uri;
|
||||
|
||||
/// Returns true if there are no matches.
|
||||
bool get isEmpty => _matches.isEmpty;
|
||||
bool get isEmpty => matches.isEmpty;
|
||||
|
||||
/// Returns true if there are matches.
|
||||
bool get isNotEmpty => _matches.isNotEmpty;
|
||||
bool get isNotEmpty => matches.isNotEmpty;
|
||||
|
||||
/// Pushes a match onto the list of matches.
|
||||
void push(RouteMatch match) {
|
||||
_matches.add(match);
|
||||
/// Returns a new instance of RouteMatchList with the input `match` pushed
|
||||
/// onto the current instance.
|
||||
RouteMatchList push<T>(ImperativeRouteMatch<T> match) {
|
||||
// Imperative route match doesn't change the uri and path parameters.
|
||||
return _copyWith(matches: <RouteMatch>[...matches, match]);
|
||||
}
|
||||
|
||||
/// Removes the match from the list.
|
||||
void remove(RouteMatch match) {
|
||||
final int index = _matches.indexOf(match);
|
||||
/// Returns a new instance of RouteMatchList with the input `match` removed
|
||||
/// from the current instance.
|
||||
RouteMatchList remove(RouteMatch match) {
|
||||
final List<RouteMatch> newMatches = matches.toList();
|
||||
final int index = newMatches.indexOf(match);
|
||||
assert(index != -1);
|
||||
_matches.removeRange(index, _matches.length);
|
||||
newMatches.removeRange(index, newMatches.length);
|
||||
|
||||
// Also pop ShellRoutes when there are no subsequent route matches
|
||||
while (_matches.isNotEmpty && _matches.last.route is ShellRoute) {
|
||||
_matches.removeLast();
|
||||
while (newMatches.isNotEmpty && newMatches.last.route is ShellRoute) {
|
||||
newMatches.removeLast();
|
||||
}
|
||||
// Removing ImperativeRouteMatch should not change uri and pathParameters.
|
||||
if (match is ImperativeRouteMatch) {
|
||||
return _copyWith(matches: newMatches);
|
||||
}
|
||||
|
||||
final String fullPath = _generateFullPath(
|
||||
_matches.where((RouteMatch match) => match is! ImperativeRouteMatch));
|
||||
newMatches.where((RouteMatch match) => match is! ImperativeRouteMatch));
|
||||
// Need to remove path parameters that are no longer in the fullPath.
|
||||
final List<String> newParameters = <String>[];
|
||||
patternToRegExp(fullPath, newParameters);
|
||||
final Set<String> validParameters = newParameters.toSet();
|
||||
pathParameters.removeWhere(
|
||||
(String key, String value) => !validParameters.contains(key));
|
||||
|
||||
_uri = _uri.replace(path: patternToPath(fullPath, pathParameters));
|
||||
final Map<String, String> newPathParameters =
|
||||
Map<String, String>.fromEntries(
|
||||
pathParameters.entries.where((MapEntry<String, String> value) =>
|
||||
validParameters.contains(value.key)),
|
||||
);
|
||||
final Uri newUri =
|
||||
uri.replace(path: patternToPath(fullPath, newPathParameters));
|
||||
return _copyWith(
|
||||
matches: newMatches,
|
||||
uri: newUri,
|
||||
pathParameters: newPathParameters,
|
||||
);
|
||||
}
|
||||
|
||||
/// An optional object provided by the app during navigation.
|
||||
Object? get extra => _matches.isEmpty ? null : _matches.last.extra;
|
||||
Object? get extra => matches.isEmpty ? null : matches.last.extra;
|
||||
|
||||
/// The last matching route.
|
||||
RouteMatch get last => _matches.last;
|
||||
|
||||
/// The route matches.
|
||||
List<RouteMatch> get matches => _matches;
|
||||
RouteMatch get last => matches.last;
|
||||
|
||||
/// Returns true if the current match intends to display an error screen.
|
||||
bool get isError => matches.length == 1 && matches.first.error != null;
|
||||
@ -170,9 +198,44 @@ class RouteMatchList {
|
||||
/// Returns the error that this match intends to display.
|
||||
Exception? get error => matches.first.error;
|
||||
|
||||
RouteMatchList _copyWith({
|
||||
List<RouteMatch>? matches,
|
||||
Uri? uri,
|
||||
Map<String, String>? pathParameters,
|
||||
}) {
|
||||
return RouteMatchList(
|
||||
matches: matches ?? this.matches,
|
||||
uri: uri ?? this.uri,
|
||||
pathParameters: pathParameters ?? this.pathParameters);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is RouteMatchList &&
|
||||
const ListEquality<RouteMatch>().equals(matches, other.matches) &&
|
||||
uri == other.uri &&
|
||||
const MapEquality<String, String>()
|
||||
.equals(pathParameters, other.pathParameters);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
Object.hashAll(matches),
|
||||
uri,
|
||||
Object.hashAllUnordered(
|
||||
pathParameters.entries.map<int>((MapEntry<String, String> entry) =>
|
||||
Object.hash(entry.key, entry.value)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${objectRuntimeType(this, 'RouteMatchList')}($fullpath)';
|
||||
return '${objectRuntimeType(this, 'RouteMatchList')}($fullPath)';
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,17 +261,17 @@ class MatcherError extends Error {
|
||||
/// For example, for a given `loc` `/a/b/c/d`, this function will return the
|
||||
/// list of [RouteBase] `[GoRouteA(), GoRouterB(), GoRouteC(), GoRouterD()]`.
|
||||
///
|
||||
/// - [loc] is the complete URL to match (without the query parameters). For
|
||||
/// example, for the URL `/a/b?c=0`, [loc] will be `/a/b`.
|
||||
/// - [restLoc] is the remaining part of the URL to match while [parentSubloc]
|
||||
/// - [location] is the complete URL to match (without the query parameters). For
|
||||
/// example, for the URL `/a/b?c=0`, [location] will be `/a/b`.
|
||||
/// - [remainingLocation] is the remaining part of the URL to match while [matchedLocation]
|
||||
/// is the part of the URL that has already been matched. For examples, for
|
||||
/// the URL `/a/b/c/d`, at some point, [restLoc] would be `/c/d` and
|
||||
/// [parentSubloc] will be `/a/b`.
|
||||
/// - [routes] are the possible [RouteBase] to match to [restLoc].
|
||||
/// the URL `/a/b/c/d`, at some point, [remainingLocation] would be `/c/d` and
|
||||
/// [matchedLocation] will be `/a/b`.
|
||||
/// - [routes] are the possible [RouteBase] to match to [remainingLocation].
|
||||
List<RouteMatch>? _getLocRouteRecursively({
|
||||
required String loc,
|
||||
required String restLoc,
|
||||
required String parentSubloc,
|
||||
required String location,
|
||||
required String remainingLocation,
|
||||
required String matchedLocation,
|
||||
required List<RouteBase> routes,
|
||||
required Map<String, String> pathParameters,
|
||||
required Object? extra,
|
||||
@ -221,8 +284,8 @@ List<RouteMatch>? _getLocRouteRecursively({
|
||||
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: restLoc,
|
||||
parentSubloc: parentSubloc,
|
||||
remainingLocation: remainingLocation,
|
||||
matchedLocation: matchedLocation,
|
||||
pathParameters: subPathParameters,
|
||||
extra: extra,
|
||||
);
|
||||
@ -232,9 +295,9 @@ List<RouteMatch>? _getLocRouteRecursively({
|
||||
}
|
||||
|
||||
if (match.route is GoRoute &&
|
||||
match.subloc.toLowerCase() == loc.toLowerCase()) {
|
||||
match.matchedLocation.toLowerCase() == location.toLowerCase()) {
|
||||
// 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 matchedLocation is canonicalized to match
|
||||
// the path case whereas the location can be of any case and still match
|
||||
result = <RouteMatch>[match];
|
||||
} else if (route.routes.isEmpty) {
|
||||
@ -245,21 +308,21 @@ List<RouteMatch>? _getLocRouteRecursively({
|
||||
final String childRestLoc;
|
||||
final String newParentSubLoc;
|
||||
if (match.route is ShellRoute) {
|
||||
childRestLoc = restLoc;
|
||||
newParentSubLoc = parentSubloc;
|
||||
childRestLoc = remainingLocation;
|
||||
newParentSubLoc = matchedLocation;
|
||||
} else {
|
||||
assert(loc.startsWith(match.subloc));
|
||||
assert(restLoc.isNotEmpty);
|
||||
assert(location.startsWith(match.matchedLocation));
|
||||
assert(remainingLocation.isNotEmpty);
|
||||
|
||||
childRestLoc =
|
||||
loc.substring(match.subloc.length + (match.subloc == '/' ? 0 : 1));
|
||||
newParentSubLoc = match.subloc;
|
||||
childRestLoc = location.substring(match.matchedLocation.length +
|
||||
(match.matchedLocation == '/' ? 0 : 1));
|
||||
newParentSubLoc = match.matchedLocation;
|
||||
}
|
||||
|
||||
final List<RouteMatch>? subRouteMatch = _getLocRouteRecursively(
|
||||
loc: loc,
|
||||
restLoc: childRestLoc,
|
||||
parentSubloc: newParentSubLoc,
|
||||
location: location,
|
||||
remainingLocation: childRestLoc,
|
||||
matchedLocation: newParentSubLoc,
|
||||
routes: route.routes,
|
||||
pathParameters: subPathParameters,
|
||||
extra: extra,
|
||||
@ -284,20 +347,21 @@ List<RouteMatch>? _getLocRouteRecursively({
|
||||
RouteMatchList errorScreen(Uri uri, String errorMessage) {
|
||||
final Exception error = Exception(errorMessage);
|
||||
return RouteMatchList(
|
||||
<RouteMatch>[
|
||||
RouteMatch(
|
||||
subloc: uri.path,
|
||||
extra: null,
|
||||
error: error,
|
||||
route: GoRoute(
|
||||
path: uri.toString(),
|
||||
pageBuilder: (BuildContext context, GoRouterState state) {
|
||||
throw UnimplementedError();
|
||||
},
|
||||
),
|
||||
pageKey: const ValueKey<String>('error'),
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
matchedLocation: uri.path,
|
||||
extra: null,
|
||||
error: error,
|
||||
route: GoRoute(
|
||||
path: uri.toString(),
|
||||
pageBuilder: (BuildContext context, GoRouterState state) {
|
||||
throw UnimplementedError();
|
||||
},
|
||||
),
|
||||
],
|
||||
uri,
|
||||
const <String, String>{});
|
||||
pageKey: const ValueKey<String>('error'),
|
||||
),
|
||||
],
|
||||
uri: uri,
|
||||
pathParameters: const <String, String>{},
|
||||
);
|
||||
}
|
||||
|
@ -12,11 +12,11 @@ extension GoRouterHelper on BuildContext {
|
||||
/// Get a location from route name and parameters.
|
||||
String namedLocation(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
}) =>
|
||||
GoRouter.of(this)
|
||||
.namedLocation(name, params: params, queryParams: queryParams);
|
||||
GoRouter.of(this).namedLocation(name,
|
||||
pathParameters: pathParameters, queryParameters: queryParameters);
|
||||
|
||||
/// Navigate to a location.
|
||||
void go(String location, {Object? extra}) =>
|
||||
@ -25,14 +25,14 @@ extension GoRouterHelper on BuildContext {
|
||||
/// Navigate to a named route.
|
||||
void goNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
GoRouter.of(this).goNamed(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: pathParameters,
|
||||
queryParameters: queryParameters,
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -50,14 +50,14 @@ extension GoRouterHelper on BuildContext {
|
||||
/// Navigate to a named route onto the page stack.
|
||||
Future<T?> pushNamed<T extends Object?>(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
GoRouter.of(this).pushNamed<T>(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: pathParameters,
|
||||
queryParameters: queryParameters,
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -81,7 +81,7 @@ extension GoRouterHelper on BuildContext {
|
||||
GoRouter.of(this).pushReplacement(location, extra: extra);
|
||||
|
||||
/// Replaces the top-most page of the page stack with the named route w/
|
||||
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
|
||||
/// optional parameters, e.g. `name='person', pathParameters={'fid': 'f2', 'pid':
|
||||
/// 'p1'}`.
|
||||
///
|
||||
/// See also:
|
||||
@ -89,14 +89,14 @@ extension GoRouterHelper on BuildContext {
|
||||
/// * [pushNamed] which pushes a named route onto the page stack.
|
||||
void pushReplacementNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
GoRouter.of(this).pushReplacementNamed(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: pathParameters,
|
||||
queryParameters: queryParameters,
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -117,8 +117,8 @@ extension GoRouterHelper on BuildContext {
|
||||
/// preserving the page key.
|
||||
///
|
||||
/// This will preserve the state and not run any page animation. Optional
|
||||
/// parameters can be providded to the named route, e.g. `name='person',
|
||||
/// params={'fid': 'f2', 'pid': 'p1'}`.
|
||||
/// parameters can be provided to the named route, e.g. `name='person',
|
||||
/// pathParameters={'fid': 'f2', 'pid': 'p1'}`.
|
||||
///
|
||||
/// See also:
|
||||
/// * [pushNamed] which pushes the given location onto the page stack.
|
||||
@ -126,8 +126,8 @@ extension GoRouterHelper on BuildContext {
|
||||
/// stack but always uses a new page key.
|
||||
void replaceNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
GoRouter.of(this).replaceNamed(name, extra: extra);
|
||||
|
@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'information_provider.dart';
|
||||
import 'logging.dart';
|
||||
import 'match.dart';
|
||||
@ -72,12 +71,12 @@ 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(
|
||||
<RouteMatch>[],
|
||||
matches: const <RouteMatch>[],
|
||||
// TODO(chunhtai): remove this ignore and migrate the code
|
||||
// https://github.com/flutter/flutter/issues/124045.
|
||||
// ignore: deprecated_member_use, unnecessary_non_null_assertion
|
||||
Uri.parse(canonicalUri(routeInformation.location!)),
|
||||
const <String, String>{},
|
||||
uri: Uri.parse(canonicalUri(routeInformation.location!)),
|
||||
pathParameters: const <String, String>{},
|
||||
);
|
||||
}
|
||||
Future<RouteMatchList> processRedirectorResult(RouteMatchList matches) {
|
||||
|
@ -96,8 +96,8 @@ 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: prevMatchList.uri.path,
|
||||
queryParams: prevMatchList.uri.queryParameters,
|
||||
matchedLocation: prevMatchList.uri.path,
|
||||
queryParameters: prevMatchList.uri.queryParameters,
|
||||
queryParametersAll: prevMatchList.uri.queryParametersAll,
|
||||
extra: extra,
|
||||
pageKey: const ValueKey<String>('topLevel'),
|
||||
@ -138,13 +138,13 @@ FutureOr<String?> _getRouteLevelRedirect(
|
||||
GoRouterState(
|
||||
configuration,
|
||||
location: matchList.uri.toString(),
|
||||
subloc: match.subloc,
|
||||
matchedLocation: match.matchedLocation,
|
||||
name: route.name,
|
||||
path: route.path,
|
||||
fullpath: matchList.fullpath,
|
||||
fullPath: matchList.fullPath,
|
||||
extra: match.extra,
|
||||
params: matchList.pathParameters,
|
||||
queryParams: matchList.uri.queryParameters,
|
||||
pathParameters: matchList.pathParameters,
|
||||
queryParameters: matchList.uri.queryParameters,
|
||||
queryParametersAll: matchList.uri.queryParametersAll,
|
||||
pageKey: match.pageKey,
|
||||
),
|
||||
|
@ -42,7 +42,7 @@ import 'typedefs.dart';
|
||||
/// GoRoute(
|
||||
/// path: 'family/:fid',
|
||||
/// pageBuilder: (BuildContext context, GoRouterState state) {
|
||||
/// final Family family = Families.family(state.params['fid']!);
|
||||
/// final Family family = Families.family(state.pathParameters['fid']!);
|
||||
/// return MaterialPage<void>(
|
||||
/// key: state.pageKey,
|
||||
/// child: FamilyPage(family: family),
|
||||
@ -52,8 +52,8 @@ import 'typedefs.dart';
|
||||
/// GoRoute(
|
||||
/// path: 'person/:pid',
|
||||
/// pageBuilder: (BuildContext context, GoRouterState state) {
|
||||
/// final Family family = Families.family(state.params['fid']!);
|
||||
/// final Person person = family.person(state.params['pid']!);
|
||||
/// final Family family = Families.family(state.pathParameters['fid']!);
|
||||
/// final Person person = family.person(state.pathParameters['pid']!);
|
||||
/// return MaterialPage<void>(
|
||||
/// key: state.pageKey,
|
||||
/// child: PersonPage(family: family, person: person),
|
||||
@ -137,7 +137,7 @@ class GoRoute extends RouteBase {
|
||||
'builder, pageBuilder, or redirect must be provided'),
|
||||
super._() {
|
||||
// cache the path regexp and parameters
|
||||
_pathRE = patternToRegExp(path, pathParams);
|
||||
_pathRE = patternToRegExp(path, pathParameters);
|
||||
}
|
||||
|
||||
/// Optional name of the route.
|
||||
@ -169,8 +169,8 @@ class GoRoute extends RouteBase {
|
||||
///
|
||||
/// context.go(
|
||||
/// context.namedLocation('family'),
|
||||
/// params: <String, String>{'fid': 123},
|
||||
/// queryParams: <String, String>{'qid': 'quid'},
|
||||
/// pathParameters: <String, String>{'fid': 123},
|
||||
/// queryParameters: <String, String>{'qid': 'quid'},
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
@ -228,7 +228,7 @@ class GoRoute extends RouteBase {
|
||||
/// path: '/',
|
||||
/// builder: (BuildContext context, GoRouterState state) => FamilyPage(
|
||||
/// families: Families.family(
|
||||
/// state.params['id'],
|
||||
/// state.pathParameters['id'],
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
@ -306,11 +306,11 @@ class GoRoute extends RouteBase {
|
||||
|
||||
/// Extract the path parameters from a match.
|
||||
Map<String, String> extractPathParams(RegExpMatch match) =>
|
||||
extractPathParameters(pathParams, match);
|
||||
extractPathParameters(pathParameters, match);
|
||||
|
||||
/// The path parameters in this route.
|
||||
@internal
|
||||
final List<String> pathParams = <String>[];
|
||||
final List<String> pathParameters = <String>[];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -8,6 +8,7 @@ import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'information_provider.dart';
|
||||
import 'logging.dart';
|
||||
import 'match.dart';
|
||||
import 'matching.dart';
|
||||
import 'misc/inherited_router.dart';
|
||||
import 'parser.dart';
|
||||
@ -179,13 +180,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
/// This is useful for redirecting to a named location.
|
||||
String namedLocation(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
}) =>
|
||||
_routeInformationParser.configuration.namedLocation(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: pathParameters,
|
||||
queryParameters: queryParameters,
|
||||
);
|
||||
|
||||
/// Navigate to a URI location w/ optional query parameters, e.g.
|
||||
@ -203,16 +204,17 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
}
|
||||
|
||||
/// Navigate to a named route w/ optional parameters, e.g.
|
||||
/// `name='person', params={'fid': 'f2', 'pid': 'p1'}`
|
||||
/// `name='person', pathParameters={'fid': 'f2', 'pid': 'p1'}`
|
||||
/// Navigate to the named route.
|
||||
void goNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
go(
|
||||
namedLocation(name, params: params, queryParams: queryParams),
|
||||
namedLocation(name,
|
||||
pathParameters: pathParameters, queryParameters: queryParameters),
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -245,15 +247,16 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
}
|
||||
|
||||
/// Push a named route onto the page stack w/ optional parameters, e.g.
|
||||
/// `name='person', params={'fid': 'f2', 'pid': 'p1'}`
|
||||
/// `name='person', pathParameters={'fid': 'f2', 'pid': 'p1'}`
|
||||
Future<T?> pushNamed<T extends Object?>(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) =>
|
||||
push<T>(
|
||||
namedLocation(name, params: params, queryParams: queryParams),
|
||||
namedLocation(name,
|
||||
pathParameters: pathParameters, queryParameters: queryParameters),
|
||||
extra: extra,
|
||||
);
|
||||
|
||||
@ -283,7 +286,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
}
|
||||
|
||||
/// Replaces the top-most page of the page stack with the named route w/
|
||||
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
|
||||
/// optional parameters, e.g. `name='person', pathParameters={'fid': 'f2', 'pid':
|
||||
/// 'p1'}`.
|
||||
///
|
||||
/// See also:
|
||||
@ -291,12 +294,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
/// * [pushNamed] which pushes a named route onto the page stack.
|
||||
void pushReplacementNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) {
|
||||
pushReplacement(
|
||||
namedLocation(name, params: params, queryParams: queryParams),
|
||||
namedLocation(name,
|
||||
pathParameters: pathParameters, queryParameters: queryParameters),
|
||||
extra: extra,
|
||||
);
|
||||
}
|
||||
@ -332,7 +336,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
///
|
||||
/// This will preserve the state and not run any page animation. Optional
|
||||
/// parameters can be providded to the named route, e.g. `name='person',
|
||||
/// params={'fid': 'f2', 'pid': 'p1'}`.
|
||||
/// pathParameters={'fid': 'f2', 'pid': 'p1'}`.
|
||||
///
|
||||
/// See also:
|
||||
/// * [pushNamed] which pushes the given location onto the page stack.
|
||||
@ -340,12 +344,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> {
|
||||
/// stack but always uses a new page key.
|
||||
void replaceNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) {
|
||||
replace(
|
||||
namedLocation(name, params: params, queryParams: queryParams),
|
||||
namedLocation(name,
|
||||
pathParameters: pathParameters, queryParameters: queryParameters),
|
||||
extra: extra,
|
||||
);
|
||||
}
|
||||
|
@ -16,12 +16,12 @@ class GoRouterState {
|
||||
const GoRouterState(
|
||||
this._configuration, {
|
||||
required this.location,
|
||||
required this.subloc,
|
||||
required this.matchedLocation,
|
||||
required this.name,
|
||||
this.path,
|
||||
this.fullpath,
|
||||
this.params = const <String, String>{},
|
||||
this.queryParams = const <String, String>{},
|
||||
this.fullPath,
|
||||
this.pathParameters = const <String, String>{},
|
||||
this.queryParameters = const <String, String>{},
|
||||
this.queryParametersAll = const <String, List<String>>{},
|
||||
this.extra,
|
||||
this.error,
|
||||
@ -35,8 +35,15 @@ class GoRouterState {
|
||||
/// The full location of the route, e.g. /family/f2/person/p1
|
||||
final String location;
|
||||
|
||||
/// The location of this sub-route, e.g. /family/f2
|
||||
final String subloc;
|
||||
/// The matched location until this point.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// location = /family/f2/person/p1
|
||||
/// route = GoRoute('/family/:id')
|
||||
///
|
||||
/// matchedLocation = /family/f2
|
||||
final String matchedLocation;
|
||||
|
||||
/// The optional name of the route.
|
||||
final String? name;
|
||||
@ -45,13 +52,13 @@ class GoRouterState {
|
||||
final String? path;
|
||||
|
||||
/// The full path to this sub-route, e.g. /family/:fid
|
||||
final String? fullpath;
|
||||
final String? fullPath;
|
||||
|
||||
/// The parameters for this sub-route, e.g. {'fid': 'f2'}
|
||||
final Map<String, String> params;
|
||||
final Map<String, String> pathParameters;
|
||||
|
||||
/// The query parameters for the location, e.g. {'from': '/family/f2'}
|
||||
final Map<String, String> queryParams;
|
||||
final Map<String, String> queryParameters;
|
||||
|
||||
/// The query parameters for the location,
|
||||
/// e.g. `{'q1': ['v1'], 'q2': ['v2', 'v3']}`
|
||||
@ -98,7 +105,7 @@ class GoRouterState {
|
||||
/// class MyWidget extends StatelessWidget {
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Text('${GoRouterState.of(context).params['id']}');
|
||||
/// return Text('${GoRouterState.of(context).pathParameters['id']}');
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@ -125,26 +132,27 @@ class GoRouterState {
|
||||
|
||||
/// Get a location from route name and parameters.
|
||||
/// This is useful for redirecting to a named location.
|
||||
@Deprecated('Use GoRouter.of(context).namedLocation instead')
|
||||
// TODO(chunhtai): remove this method when go_router can provide a way to
|
||||
// look up named location during redirect.
|
||||
String namedLocation(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, String> queryParams = const <String, String>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, String> queryParameters = const <String, String>{},
|
||||
}) {
|
||||
return _configuration.namedLocation(name,
|
||||
params: params, queryParams: queryParams);
|
||||
pathParameters: pathParameters, queryParameters: queryParameters);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GoRouterState &&
|
||||
other.location == location &&
|
||||
other.subloc == subloc &&
|
||||
other.matchedLocation == matchedLocation &&
|
||||
other.name == name &&
|
||||
other.path == path &&
|
||||
other.fullpath == fullpath &&
|
||||
other.params == params &&
|
||||
other.queryParams == queryParams &&
|
||||
other.fullPath == fullPath &&
|
||||
other.pathParameters == pathParameters &&
|
||||
other.queryParameters == queryParameters &&
|
||||
other.queryParametersAll == queryParametersAll &&
|
||||
other.extra == extra &&
|
||||
other.error == error &&
|
||||
@ -152,8 +160,18 @@ class GoRouterState {
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(location, subloc, name, path, fullpath,
|
||||
params, queryParams, queryParametersAll, extra, error, pageKey);
|
||||
int get hashCode => Object.hash(
|
||||
location,
|
||||
matchedLocation,
|
||||
name,
|
||||
path,
|
||||
fullPath,
|
||||
pathParameters,
|
||||
queryParameters,
|
||||
queryParametersAll,
|
||||
extra,
|
||||
error,
|
||||
pageKey);
|
||||
}
|
||||
|
||||
/// An inherited widget to host a [GoRouterStateRegistry] for the subtree.
|
||||
|
@ -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: 6.5.9
|
||||
version: 7.0.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
|
||||
|
||||
@ -21,3 +21,4 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
path: ^1.8.2
|
||||
|
@ -29,17 +29,17 @@ void main() {
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first as GoRoute,
|
||||
subloc: '/',
|
||||
matchedLocation: '/',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
],
|
||||
Uri.parse('/'),
|
||||
const <String, String>{});
|
||||
uri: Uri.parse('/'),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -76,17 +76,17 @@ void main() {
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first,
|
||||
subloc: '/',
|
||||
matchedLocation: '/',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
],
|
||||
Uri.parse('/'),
|
||||
<String, String>{});
|
||||
uri: Uri.parse('/'),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -118,17 +118,17 @@ void main() {
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first as GoRoute,
|
||||
subloc: '/',
|
||||
matchedLocation: '/',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/'),
|
||||
),
|
||||
],
|
||||
Uri.parse('/'),
|
||||
<String, String>{});
|
||||
uri: Uri.parse('/'),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -173,24 +173,24 @@ void main() {
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first,
|
||||
subloc: '',
|
||||
matchedLocation: '',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>(''),
|
||||
),
|
||||
RouteMatch(
|
||||
route: config.routes.first.routes.first,
|
||||
subloc: '/details',
|
||||
matchedLocation: '/details',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/details'),
|
||||
),
|
||||
],
|
||||
Uri.parse('/details'),
|
||||
<String, String>{});
|
||||
uri: Uri.parse('/details'),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
@ -248,17 +248,17 @@ void main() {
|
||||
);
|
||||
|
||||
final RouteMatchList matches = RouteMatchList(
|
||||
<RouteMatch>[
|
||||
matches: <RouteMatch>[
|
||||
RouteMatch(
|
||||
route: config.routes.first.routes.first as GoRoute,
|
||||
subloc: '/a/details',
|
||||
matchedLocation: '/a/details',
|
||||
extra: null,
|
||||
error: null,
|
||||
pageKey: const ValueKey<String>('/a/details'),
|
||||
),
|
||||
],
|
||||
Uri.parse('/a/details'),
|
||||
<String, String>{});
|
||||
uri: Uri.parse('/a/details'),
|
||||
pathParameters: const <String, String>{});
|
||||
|
||||
await tester.pumpWidget(
|
||||
_BuilderTestWidget(
|
||||
|
@ -5,7 +5,6 @@
|
||||
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';
|
||||
|
||||
|
@ -17,13 +17,13 @@ void main() {
|
||||
path: '/',
|
||||
builder: (BuildContext context, _) {
|
||||
final GoRouterState state = GoRouterState.of(context);
|
||||
return Text('/ ${state.queryParams['p']}');
|
||||
return Text('/ ${state.queryParameters['p']}');
|
||||
}),
|
||||
GoRoute(
|
||||
path: '/a',
|
||||
builder: (BuildContext context, _) {
|
||||
final GoRouterState state = GoRouterState.of(context);
|
||||
return Text('/a ${state.queryParams['p']}');
|
||||
return Text('/a ${state.queryParameters['p']}');
|
||||
}),
|
||||
];
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
@ -83,7 +83,7 @@ void main() {
|
||||
builder: (_, __) {
|
||||
return Builder(builder: (BuildContext context) {
|
||||
return Text(
|
||||
'2 ${GoRouterState.of(context).params['id']}');
|
||||
'2 ${GoRouterState.of(context).pathParameters['id']}');
|
||||
});
|
||||
}),
|
||||
]),
|
||||
|
@ -11,7 +11,6 @@ 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';
|
||||
@ -152,7 +151,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.subloc, '/login');
|
||||
expect(matches.first.matchedLocation, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -181,7 +180,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.subloc, '/login');
|
||||
expect(matches.first.matchedLocation, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -205,7 +204,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.subloc, '/login');
|
||||
expect(matches.first.matchedLocation, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -224,7 +223,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.subloc, '/profile/foo');
|
||||
expect(matches.first.matchedLocation, '/profile/foo');
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -243,7 +242,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches, hasLength(1));
|
||||
expect(matches.first.subloc, '/profile/foo');
|
||||
expect(matches.first.matchedLocation, '/profile/foo');
|
||||
expect(find.byType(DummyScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -355,9 +354,9 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
expect(matches.length, 2);
|
||||
expect(matches.first.subloc, '/');
|
||||
expect(matches.first.matchedLocation, '/');
|
||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches[1].subloc, '/login');
|
||||
expect(matches[1].matchedLocation, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -402,9 +401,9 @@ void main() {
|
||||
{
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 2);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
expect(matches.matches.first.matchedLocation, '/');
|
||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches.matches[1].subloc, '/login');
|
||||
expect(matches.matches[1].matchedLocation, '/login');
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
}
|
||||
|
||||
@ -413,9 +412,9 @@ void main() {
|
||||
{
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 2);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
expect(matches.matches.first.matchedLocation, '/');
|
||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches.matches[1].subloc, '/family/f2');
|
||||
expect(matches.matches[1].matchedLocation, '/family/f2');
|
||||
expect(find.byType(FamilyScreen), findsOneWidget);
|
||||
}
|
||||
|
||||
@ -424,11 +423,11 @@ void main() {
|
||||
{
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches.length, 3);
|
||||
expect(matches.matches.first.subloc, '/');
|
||||
expect(matches.matches.first.matchedLocation, '/');
|
||||
expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches.matches[1].subloc, '/family/f2');
|
||||
expect(matches.matches[1].matchedLocation, '/family/f2');
|
||||
expect(find.byType(FamilyScreen, skipOffstage: false), findsOneWidget);
|
||||
expect(matches.matches[2].subloc, '/family/f2/person/p1');
|
||||
expect(matches.matches[2].matchedLocation, '/family/f2/person/p1');
|
||||
expect(find.byType(PersonScreen), findsOneWidget);
|
||||
}
|
||||
});
|
||||
@ -494,11 +493,11 @@ void main() {
|
||||
path: '/',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.location, '/');
|
||||
expect(state.subloc, '/');
|
||||
expect(state.matchedLocation, '/');
|
||||
expect(state.name, 'home');
|
||||
expect(state.path, '/');
|
||||
expect(state.fullpath, '/');
|
||||
expect(state.params, <String, String>{});
|
||||
expect(state.fullPath, '/');
|
||||
expect(state.pathParameters, <String, String>{});
|
||||
expect(state.error, null);
|
||||
if (state.extra != null) {
|
||||
expect(state.extra! as int, 1);
|
||||
@ -511,11 +510,11 @@ void main() {
|
||||
path: 'login',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.location, '/login');
|
||||
expect(state.subloc, '/login');
|
||||
expect(state.matchedLocation, '/login');
|
||||
expect(state.name, 'login');
|
||||
expect(state.path, 'login');
|
||||
expect(state.fullpath, '/login');
|
||||
expect(state.params, <String, String>{});
|
||||
expect(state.fullPath, '/login');
|
||||
expect(state.pathParameters, <String, String>{});
|
||||
expect(state.error, null);
|
||||
expect(state.extra! as int, 2);
|
||||
return const LoginScreen();
|
||||
@ -529,14 +528,14 @@ void main() {
|
||||
state.location,
|
||||
anyOf(<String>['/family/f2', '/family/f2/person/p1']),
|
||||
);
|
||||
expect(state.subloc, '/family/f2');
|
||||
expect(state.matchedLocation, '/family/f2');
|
||||
expect(state.name, 'family');
|
||||
expect(state.path, 'family/:fid');
|
||||
expect(state.fullpath, '/family/:fid');
|
||||
expect(state.params, <String, String>{'fid': 'f2'});
|
||||
expect(state.fullPath, '/family/:fid');
|
||||
expect(state.pathParameters, <String, String>{'fid': 'f2'});
|
||||
expect(state.error, null);
|
||||
expect(state.extra! as int, 3);
|
||||
return FamilyScreen(state.params['fid']!);
|
||||
return FamilyScreen(state.pathParameters['fid']!);
|
||||
},
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
@ -544,18 +543,18 @@ void main() {
|
||||
path: 'person/:pid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.location, '/family/f2/person/p1');
|
||||
expect(state.subloc, '/family/f2/person/p1');
|
||||
expect(state.matchedLocation, '/family/f2/person/p1');
|
||||
expect(state.name, 'person');
|
||||
expect(state.path, 'person/:pid');
|
||||
expect(state.fullpath, '/family/:fid/person/:pid');
|
||||
expect(state.fullPath, '/family/:fid/person/:pid');
|
||||
expect(
|
||||
state.params,
|
||||
state.pathParameters,
|
||||
<String, String>{'fid': 'f2', 'pid': 'p1'},
|
||||
);
|
||||
expect(state.error, null);
|
||||
expect(state.extra! as int, 4);
|
||||
return PersonScreen(
|
||||
state.params['fid']!, state.params['pid']!);
|
||||
return PersonScreen(state.pathParameters['fid']!,
|
||||
state.pathParameters['pid']!);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -585,7 +584,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
FamilyScreen(state.params['fid']!),
|
||||
FamilyScreen(state.pathParameters['fid']!),
|
||||
),
|
||||
];
|
||||
|
||||
@ -595,7 +594,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
|
||||
// NOTE: match the lower case, since subloc is canonicalized to match the
|
||||
// NOTE: match the lower case, since location is canonicalized to match the
|
||||
// path case whereas the location can be any case; so long as the path
|
||||
// produces a match regardless of the location case, we win!
|
||||
expect(router.location.toLowerCase(), loc.toLowerCase());
|
||||
@ -1321,7 +1320,7 @@ void main() {
|
||||
name: 'person',
|
||||
path: 'person/:pid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.params,
|
||||
expect(state.pathParameters,
|
||||
<String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
return const PersonScreen('dummy', 'dummy');
|
||||
},
|
||||
@ -1334,7 +1333,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.goNamed('person',
|
||||
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
pathParameters: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
});
|
||||
|
||||
testWidgets('too few params', (WidgetTester tester) async {
|
||||
@ -1364,7 +1363,7 @@ void main() {
|
||||
];
|
||||
await expectLater(() async {
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.goNamed('person', params: <String, String>{'fid': 'f2'});
|
||||
router.goNamed('person', pathParameters: <String, String>{'fid': 'f2'});
|
||||
await tester.pump();
|
||||
}, throwsA(isAssertionError));
|
||||
});
|
||||
@ -1388,7 +1387,7 @@ void main() {
|
||||
name: 'PeRsOn',
|
||||
path: 'person/:pid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.params,
|
||||
expect(state.pathParameters,
|
||||
<String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
return const PersonScreen('dummy', 'dummy');
|
||||
},
|
||||
@ -1401,7 +1400,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.goNamed('person',
|
||||
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
pathParameters: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
});
|
||||
|
||||
testWidgets('too few params', (WidgetTester tester) async {
|
||||
@ -1431,7 +1430,7 @@ void main() {
|
||||
await expectLater(() async {
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.goNamed('family',
|
||||
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
pathParameters: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
}, throwsA(isAssertionError));
|
||||
});
|
||||
|
||||
@ -1445,7 +1444,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) => FamilyScreen(
|
||||
state.params['fid']!,
|
||||
state.pathParameters['fid']!,
|
||||
),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
@ -1453,8 +1452,8 @@ void main() {
|
||||
path: 'person:pid',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
PersonScreen(
|
||||
state.params['fid']!,
|
||||
state.params['pid']!,
|
||||
state.pathParameters['fid']!,
|
||||
state.pathParameters['pid']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1463,7 +1462,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
router.goNamed('person',
|
||||
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
pathParameters: <String, String>{'fid': 'f2', 'pid': 'p1'});
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PersonScreen), findsOneWidget);
|
||||
});
|
||||
@ -1476,15 +1475,15 @@ void main() {
|
||||
name: 'page1',
|
||||
path: '/page1/:param1',
|
||||
builder: (BuildContext c, GoRouterState s) {
|
||||
expect(s.params['param1'], param1);
|
||||
expect(s.pathParameters['param1'], param1);
|
||||
return const DummyScreen();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
final String loc = router
|
||||
.namedLocation('page1', params: <String, String>{'param1': param1});
|
||||
final String loc = router.namedLocation('page1',
|
||||
pathParameters: <String, String>{'param1': param1});
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@ -1501,7 +1500,7 @@ void main() {
|
||||
name: 'page1',
|
||||
path: '/page1',
|
||||
builder: (BuildContext c, GoRouterState s) {
|
||||
expect(s.queryParams['param1'], param1);
|
||||
expect(s.queryParameters['param1'], param1);
|
||||
return const DummyScreen();
|
||||
},
|
||||
),
|
||||
@ -1509,7 +1508,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
final String loc = router.namedLocation('page1',
|
||||
queryParams: <String, String>{'param1': param1});
|
||||
queryParameters: <String, String>{'param1': param1});
|
||||
router.go(loc);
|
||||
await tester.pumpAndSettle();
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
@ -1542,7 +1541,7 @@ void main() {
|
||||
final GoRouter router = await createRouter(routes, tester,
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
redirected = true;
|
||||
return state.subloc == '/login' ? null : '/login';
|
||||
return state.matchedLocation == '/login' ? null : '/login';
|
||||
});
|
||||
|
||||
expect(router.location, '/login');
|
||||
@ -1617,7 +1616,9 @@ void main() {
|
||||
routes,
|
||||
tester,
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
state.subloc == '/login' ? null : state.namedLocation('login'),
|
||||
state.matchedLocation == '/login'
|
||||
? null
|
||||
: state.namedLocation('login'),
|
||||
);
|
||||
expect(router.location, '/login');
|
||||
});
|
||||
@ -1682,7 +1683,7 @@ void main() {
|
||||
final GoRouter router = await createRouter(routes, tester,
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
redirected = true;
|
||||
return state.subloc == '/login' ? null : '/login';
|
||||
return state.matchedLocation == '/login' ? null : '/login';
|
||||
});
|
||||
redirected = false;
|
||||
// Directly set the url through platform message.
|
||||
@ -1750,7 +1751,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester,
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
state.subloc == '/dummy1' ? '/dummy2' : null);
|
||||
state.matchedLocation == '/dummy1' ? '/dummy2' : null);
|
||||
router.go('/dummy1');
|
||||
await tester.pump();
|
||||
expect(router.location, '/');
|
||||
@ -1759,9 +1760,9 @@ void main() {
|
||||
testWidgets('top-level redirect loop', (WidgetTester tester) async {
|
||||
final GoRouter router = await createRouter(<GoRoute>[], tester,
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
state.subloc == '/'
|
||||
state.matchedLocation == '/'
|
||||
? '/login'
|
||||
: state.subloc == '/login'
|
||||
: state.matchedLocation == '/login'
|
||||
? '/'
|
||||
: null);
|
||||
|
||||
@ -1809,7 +1810,7 @@ void main() {
|
||||
],
|
||||
tester,
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
state.subloc == '/' ? '/login' : null,
|
||||
state.matchedLocation == '/' ? '/login' : null,
|
||||
);
|
||||
|
||||
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
|
||||
@ -1826,9 +1827,9 @@ void main() {
|
||||
<GoRoute>[],
|
||||
tester,
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
state.subloc == '/'
|
||||
state.matchedLocation == '/'
|
||||
? '/login?from=${state.location}'
|
||||
: state.subloc == '/login'
|
||||
: state.matchedLocation == '/login'
|
||||
? '/'
|
||||
: null,
|
||||
);
|
||||
@ -1841,7 +1842,7 @@ void main() {
|
||||
expect(screen.ex, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('expect null path/fullpath on top-level redirect',
|
||||
testWidgets('expect null path/fullPath on top-level redirect',
|
||||
(WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(
|
||||
@ -1884,12 +1885,12 @@ void main() {
|
||||
initialLocation: '/login?from=/',
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
expect(Uri.parse(state.location).queryParameters, isNotEmpty);
|
||||
expect(Uri.parse(state.subloc).queryParameters, isEmpty);
|
||||
expect(Uri.parse(state.matchedLocation).queryParameters, isEmpty);
|
||||
expect(state.path, isNull);
|
||||
expect(state.fullpath, isNull);
|
||||
expect(state.params.length, 0);
|
||||
expect(state.queryParams.length, 1);
|
||||
expect(state.queryParams['from'], '/');
|
||||
expect(state.fullPath, isNull);
|
||||
expect(state.pathParameters.length, 0);
|
||||
expect(state.queryParameters.length, 1);
|
||||
expect(state.queryParameters['from'], '/');
|
||||
return null;
|
||||
},
|
||||
);
|
||||
@ -1906,11 +1907,11 @@ void main() {
|
||||
path: '/book/:bookId',
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
expect(state.location, loc);
|
||||
expect(state.subloc, loc);
|
||||
expect(state.matchedLocation, loc);
|
||||
expect(state.path, '/book/:bookId');
|
||||
expect(state.fullpath, '/book/:bookId');
|
||||
expect(state.params, <String, String>{'bookId': '0'});
|
||||
expect(state.queryParams.length, 0);
|
||||
expect(state.fullPath, '/book/:bookId');
|
||||
expect(state.pathParameters, <String, String>{'bookId': '0'});
|
||||
expect(state.queryParameters.length, 0);
|
||||
return null;
|
||||
},
|
||||
builder: (BuildContext c, GoRouterState s) => const HomeScreen(),
|
||||
@ -1938,18 +1939,18 @@ void main() {
|
||||
GoRoute(
|
||||
path: 'family/:fid',
|
||||
builder: (BuildContext c, GoRouterState s) =>
|
||||
FamilyScreen(s.params['fid']!),
|
||||
FamilyScreen(s.pathParameters['fid']!),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: 'person/:pid',
|
||||
redirect: (BuildContext context, GoRouterState s) {
|
||||
expect(s.params['fid'], 'f2');
|
||||
expect(s.params['pid'], 'p1');
|
||||
expect(s.pathParameters['fid'], 'f2');
|
||||
expect(s.pathParameters['pid'], 'p1');
|
||||
return null;
|
||||
},
|
||||
builder: (BuildContext c, GoRouterState s) => PersonScreen(
|
||||
s.params['fid']!,
|
||||
s.params['pid']!,
|
||||
s.pathParameters['fid']!,
|
||||
s.pathParameters['pid']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -2221,7 +2222,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
FamilyScreen(state.params['fid']!),
|
||||
FamilyScreen(state.pathParameters['fid']!),
|
||||
),
|
||||
];
|
||||
|
||||
@ -2249,7 +2250,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/family',
|
||||
builder: (BuildContext context, GoRouterState state) => FamilyScreen(
|
||||
state.queryParams['fid']!,
|
||||
state.queryParameters['fid']!,
|
||||
),
|
||||
),
|
||||
];
|
||||
@ -2275,7 +2276,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/page1/:param1',
|
||||
builder: (BuildContext c, GoRouterState s) {
|
||||
expect(s.params['param1'], param1);
|
||||
expect(s.pathParameters['param1'], param1);
|
||||
return const DummyScreen();
|
||||
},
|
||||
),
|
||||
@ -2298,7 +2299,7 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/page1',
|
||||
builder: (BuildContext c, GoRouterState s) {
|
||||
expect(s.queryParams['param1'], param1);
|
||||
expect(s.queryParameters['param1'], param1);
|
||||
return const DummyScreen();
|
||||
},
|
||||
),
|
||||
@ -2346,10 +2347,10 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
log.info('id= ${state.params['id']}');
|
||||
expect(state.params.length, 0);
|
||||
expect(state.queryParams.length, 1);
|
||||
expect(state.queryParams['id'], anyOf('0', '1'));
|
||||
log.info('id= ${state.pathParameters['id']}');
|
||||
expect(state.pathParameters.length, 0);
|
||||
expect(state.queryParameters.length, 1);
|
||||
expect(state.queryParameters['id'], anyOf('0', '1'));
|
||||
return const HomeScreen();
|
||||
},
|
||||
),
|
||||
@ -2359,7 +2360,7 @@ void main() {
|
||||
);
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.fullpath, '/');
|
||||
expect(matches.fullPath, '/');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -2369,8 +2370,8 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/:id',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
expect(state.params, <String, String>{'id': '0'});
|
||||
expect(state.queryParams, <String, String>{'id': '1'});
|
||||
expect(state.pathParameters, <String, String>{'id': '0'});
|
||||
expect(state.queryParameters, <String, String>{'id': '1'});
|
||||
return const HomeScreen();
|
||||
},
|
||||
),
|
||||
@ -2382,7 +2383,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.matches, hasLength(1));
|
||||
expect(matches.fullpath, '/:id');
|
||||
expect(matches.fullPath, '/:id');
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@ -2394,15 +2395,15 @@ void main() {
|
||||
path: '/family',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
FamilyScreen(
|
||||
state.queryParams['fid']!,
|
||||
state.queryParameters['fid']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/person',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
PersonScreen(
|
||||
state.queryParams['fid']!,
|
||||
state.queryParams['pid']!,
|
||||
state.queryParameters['fid']!,
|
||||
state.queryParameters['pid']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -2470,13 +2471,13 @@ void main() {
|
||||
GoRoute(
|
||||
path: '/family/:fid',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
FamilyScreen(state.params['fid']!),
|
||||
FamilyScreen(state.pathParameters['fid']!),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: 'person/:pid',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final String fid = state.params['fid']!;
|
||||
final String pid = state.params['pid']!;
|
||||
final String fid = state.pathParameters['fid']!;
|
||||
final String pid = state.pathParameters['pid']!;
|
||||
|
||||
return PersonScreen(fid, pid);
|
||||
},
|
||||
@ -2536,7 +2537,7 @@ void main() {
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
|
||||
router.goNamed('page', queryParams: const <String, dynamic>{
|
||||
router.goNamed('page', queryParameters: const <String, dynamic>{
|
||||
'q1': 'v1',
|
||||
'q2': <String>['v2', 'v3'],
|
||||
});
|
||||
@ -2694,12 +2695,12 @@ void main() {
|
||||
);
|
||||
key.currentContext!.namedLocation(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: params,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
expect(router.name, name);
|
||||
expect(router.params, params);
|
||||
expect(router.queryParams, queryParams);
|
||||
expect(router.pathParameters, params);
|
||||
expect(router.queryParameters, queryParams);
|
||||
});
|
||||
|
||||
testWidgets('calls [go] on closest GoRouter', (WidgetTester tester) async {
|
||||
@ -2729,13 +2730,13 @@ void main() {
|
||||
);
|
||||
key.currentContext!.goNamed(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: params,
|
||||
queryParameters: queryParams,
|
||||
extra: extra,
|
||||
);
|
||||
expect(router.name, name);
|
||||
expect(router.params, params);
|
||||
expect(router.queryParams, queryParams);
|
||||
expect(router.pathParameters, params);
|
||||
expect(router.queryParameters, queryParams);
|
||||
expect(router.extra, extra);
|
||||
});
|
||||
|
||||
@ -2787,13 +2788,13 @@ void main() {
|
||||
);
|
||||
key.currentContext!.pushNamed(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: params,
|
||||
queryParameters: queryParams,
|
||||
extra: extra,
|
||||
);
|
||||
expect(router.name, name);
|
||||
expect(router.params, params);
|
||||
expect(router.queryParams, queryParams);
|
||||
expect(router.pathParameters, params);
|
||||
expect(router.queryParameters, queryParams);
|
||||
expect(router.extra, extra);
|
||||
});
|
||||
|
||||
@ -2810,15 +2811,15 @@ void main() {
|
||||
);
|
||||
final String? result = await router.pushNamed<String>(
|
||||
name,
|
||||
params: params,
|
||||
queryParams: queryParams,
|
||||
pathParameters: params,
|
||||
queryParameters: queryParams,
|
||||
extra: extra,
|
||||
);
|
||||
expect(result, extra);
|
||||
expect(router.extra, extra);
|
||||
expect(router.name, name);
|
||||
expect(router.params, params);
|
||||
expect(router.queryParams, queryParams);
|
||||
expect(router.pathParameters, params);
|
||||
expect(router.queryParameters, queryParams);
|
||||
});
|
||||
|
||||
testWidgets('calls [pop] on closest GoRouter', (WidgetTester tester) async {
|
||||
|
@ -130,8 +130,8 @@ class MockGoRouter extends GoRouter {
|
||||
|
||||
@override
|
||||
Future<T?> pushNamed<T extends Object?>(String name,
|
||||
{Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
{Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra}) {
|
||||
latestPushedName = name;
|
||||
return Future<T?>.value();
|
||||
|
@ -17,8 +17,8 @@ void main() {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: '/users/123',
|
||||
parentSubloc: '',
|
||||
remainingLocation: '/users/123',
|
||||
matchedLocation: '',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
@ -26,14 +26,14 @@ void main() {
|
||||
fail('Null match');
|
||||
}
|
||||
expect(match.route, route);
|
||||
expect(match.subloc, '/users/123');
|
||||
expect(match.matchedLocation, '/users/123');
|
||||
expect(pathParameters['userId'], '123');
|
||||
expect(match.extra, const _Extra('foo'));
|
||||
expect(match.error, isNull);
|
||||
expect(match.pageKey, isNotNull);
|
||||
});
|
||||
|
||||
test('subloc', () {
|
||||
test('matchedLocation', () {
|
||||
final GoRoute route = GoRoute(
|
||||
path: 'users/:userId',
|
||||
builder: _builder,
|
||||
@ -41,8 +41,8 @@ void main() {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/123',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
@ -50,7 +50,7 @@ void main() {
|
||||
fail('Null match');
|
||||
}
|
||||
expect(match.route, route);
|
||||
expect(match.subloc, '/home/users/123');
|
||||
expect(match.matchedLocation, '/home/users/123');
|
||||
expect(pathParameters['userId'], '123');
|
||||
expect(match.extra, const _Extra('foo'));
|
||||
expect(match.error, isNull);
|
||||
@ -70,8 +70,8 @@ void main() {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/123',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
@ -94,16 +94,16 @@ void main() {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match1 = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/123',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
|
||||
final RouteMatch? match2 = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/1234',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/1234',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo1'),
|
||||
);
|
||||
@ -119,16 +119,16 @@ void main() {
|
||||
final Map<String, String> pathParameters = <String, String>{};
|
||||
final RouteMatch? match1 = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/123',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/123',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo'),
|
||||
);
|
||||
|
||||
final RouteMatch? match2 = RouteMatch.match(
|
||||
route: route,
|
||||
restLoc: 'users/1234',
|
||||
parentSubloc: '/home',
|
||||
remainingLocation: 'users/1234',
|
||||
matchedLocation: '/home',
|
||||
pathParameters: pathParameters,
|
||||
extra: const _Extra('foo1'),
|
||||
);
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/src/configuration.dart';
|
||||
import 'package:go_router/src/match.dart';
|
||||
import 'package:go_router/src/matching.dart';
|
||||
import 'package:go_router/src/router.dart';
|
||||
|
||||
@ -27,4 +28,50 @@ void main() {
|
||||
final RouteMatchList matches = router.routerDelegate.matches;
|
||||
expect(matches.toString(), contains('/page-0'));
|
||||
});
|
||||
|
||||
test('RouteMatchList compares', () async {
|
||||
final GoRoute route = GoRoute(
|
||||
path: '/page-0',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const Placeholder(),
|
||||
);
|
||||
final Map<String, String> params1 = <String, String>{};
|
||||
final RouteMatch match1 = RouteMatch.match(
|
||||
route: route,
|
||||
remainingLocation: '/page-0',
|
||||
matchedLocation: '',
|
||||
pathParameters: params1,
|
||||
extra: null,
|
||||
)!;
|
||||
|
||||
final Map<String, String> params2 = <String, String>{};
|
||||
final RouteMatch match2 = RouteMatch.match(
|
||||
route: route,
|
||||
remainingLocation: '/page-0',
|
||||
matchedLocation: '',
|
||||
pathParameters: params2,
|
||||
extra: null,
|
||||
)!;
|
||||
|
||||
final RouteMatchList matches1 = RouteMatchList(
|
||||
matches: <RouteMatch>[match1],
|
||||
uri: Uri.parse(''),
|
||||
pathParameters: params1,
|
||||
);
|
||||
|
||||
final RouteMatchList matches2 = RouteMatchList(
|
||||
matches: <RouteMatch>[match2],
|
||||
uri: Uri.parse(''),
|
||||
pathParameters: params2,
|
||||
);
|
||||
|
||||
final RouteMatchList matches3 = RouteMatchList(
|
||||
matches: <RouteMatch>[match2],
|
||||
uri: Uri.parse('/page-0'),
|
||||
pathParameters: params2,
|
||||
);
|
||||
|
||||
expect(matches1 == matches2, isTrue);
|
||||
expect(matches1 == matches3, isFalse);
|
||||
});
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ void main() {
|
||||
expect(matches.length, 1);
|
||||
expect(matchesObj.uri.toString(), '/');
|
||||
expect(matches[0].extra, isNull);
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].matchedLocation, '/');
|
||||
expect(matches[0].route, routes[0]);
|
||||
|
||||
final Object extra = Object();
|
||||
@ -75,11 +75,11 @@ void main() {
|
||||
expect(matches.length, 2);
|
||||
expect(matchesObj.uri.toString(), '/abc?def=ghi');
|
||||
expect(matches[0].extra, extra);
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].matchedLocation, '/');
|
||||
expect(matches[0].route, routes[0]);
|
||||
|
||||
expect(matches[1].extra, extra);
|
||||
expect(matches[1].subloc, '/abc');
|
||||
expect(matches[1].matchedLocation, '/abc');
|
||||
expect(matches[1].route, routes[0].routes[0]);
|
||||
});
|
||||
|
||||
@ -126,11 +126,11 @@ void main() {
|
||||
expect(configuration.namedLocation('lowercase'), '/abc');
|
||||
expect(
|
||||
configuration.namedLocation('lowercase',
|
||||
queryParams: const <String, String>{'q': '1'}),
|
||||
queryParameters: const <String, String>{'q': '1'}),
|
||||
'/abc?q=1');
|
||||
expect(
|
||||
configuration.namedLocation('lowercase',
|
||||
queryParams: const <String, String>{'q': '1', 'g': '2'}),
|
||||
queryParameters: const <String, String>{'q': '1', 'g': '2'}),
|
||||
'/abc?q=1&g=2');
|
||||
});
|
||||
|
||||
@ -160,7 +160,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
configuration
|
||||
.namedLocation('routeName', queryParams: const <String, dynamic>{
|
||||
.namedLocation('routeName', queryParameters: const <String, dynamic>{
|
||||
'q1': 'v1',
|
||||
'q2': <String>['v2', 'v3'],
|
||||
}),
|
||||
@ -198,7 +198,7 @@ void main() {
|
||||
expect(matches.length, 1);
|
||||
expect(matchesObj.uri.toString(), '/def');
|
||||
expect(matches[0].extra, isNull);
|
||||
expect(matches[0].subloc, '/def');
|
||||
expect(matches[0].matchedLocation, '/def');
|
||||
expect(matches[0].error!.toString(),
|
||||
'Exception: no routes for location: /def');
|
||||
});
|
||||
@ -268,10 +268,10 @@ void main() {
|
||||
expect(matchesObj.pathParameters['uid'], '123');
|
||||
expect(matchesObj.pathParameters['fid'], '456');
|
||||
expect(matches[0].extra, isNull);
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].matchedLocation, '/');
|
||||
|
||||
expect(matches[1].extra, isNull);
|
||||
expect(matches[1].subloc, '/123/family/456');
|
||||
expect(matches[1].matchedLocation, '/123/family/456');
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@ -309,9 +309,9 @@ void main() {
|
||||
|
||||
expect(matches.length, 2);
|
||||
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].matchedLocation, '/');
|
||||
|
||||
expect(matches[1].subloc, '/123/family/345');
|
||||
expect(matches[1].matchedLocation, '/123/family/345');
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@ -349,9 +349,9 @@ void main() {
|
||||
|
||||
expect(matches.length, 2);
|
||||
expect(matchesObj.uri.toString(), '/123/family/345');
|
||||
expect(matches[0].subloc, '/');
|
||||
expect(matches[0].matchedLocation, '/');
|
||||
|
||||
expect(matches[1].subloc, '/123/family/345');
|
||||
expect(matches[1].matchedLocation, '/123/family/345');
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
|
@ -37,18 +37,18 @@ class GoRouterNamedLocationSpy extends GoRouter {
|
||||
GoRouterNamedLocationSpy({required super.routes});
|
||||
|
||||
String? name;
|
||||
Map<String, String>? params;
|
||||
Map<String, dynamic>? queryParams;
|
||||
Map<String, String>? pathParameters;
|
||||
Map<String, dynamic>? queryParameters;
|
||||
|
||||
@override
|
||||
String namedLocation(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
}) {
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.queryParams = queryParams;
|
||||
this.pathParameters = pathParameters;
|
||||
this.queryParameters = queryParameters;
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@ -70,20 +70,20 @@ class GoRouterGoNamedSpy extends GoRouter {
|
||||
GoRouterGoNamedSpy({required super.routes});
|
||||
|
||||
String? name;
|
||||
Map<String, String>? params;
|
||||
Map<String, dynamic>? queryParams;
|
||||
Map<String, String>? pathParameters;
|
||||
Map<String, dynamic>? queryParameters;
|
||||
Object? extra;
|
||||
|
||||
@override
|
||||
void goNamed(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) {
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.queryParams = queryParams;
|
||||
this.pathParameters = pathParameters;
|
||||
this.queryParameters = queryParameters;
|
||||
this.extra = extra;
|
||||
}
|
||||
}
|
||||
@ -106,20 +106,20 @@ class GoRouterPushNamedSpy extends GoRouter {
|
||||
GoRouterPushNamedSpy({required super.routes});
|
||||
|
||||
String? name;
|
||||
Map<String, String>? params;
|
||||
Map<String, dynamic>? queryParams;
|
||||
Map<String, String>? pathParameters;
|
||||
Map<String, dynamic>? queryParameters;
|
||||
Object? extra;
|
||||
|
||||
@override
|
||||
Future<T?> pushNamed<T extends Object?>(
|
||||
String name, {
|
||||
Map<String, String> params = const <String, String>{},
|
||||
Map<String, dynamic> queryParams = const <String, dynamic>{},
|
||||
Map<String, String> pathParameters = const <String, String>{},
|
||||
Map<String, dynamic> queryParameters = const <String, dynamic>{},
|
||||
Object? extra,
|
||||
}) {
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.queryParams = queryParams;
|
||||
this.pathParameters = pathParameters;
|
||||
this.queryParameters = queryParameters;
|
||||
this.extra = extra;
|
||||
return Future<T?>.value(extra as T?);
|
||||
}
|
||||
|
17
packages/go_router/test_fixes/README.md
Normal file
17
packages/go_router/test_fixes/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Directory contents
|
||||
|
||||
The Dart files and golden master `.expect` files in this directory are used to
|
||||
test the [`dart fix` framework](https://dart.dev/tools/dart-fix) refactorings
|
||||
used by the go_router package
|
||||
|
||||
See the packages/packages/go_router/lib/fix_data.yaml directory for the current
|
||||
package:go_router data-driven fixes.
|
||||
|
||||
To run these tests locally, execute this command in the
|
||||
packages/packages/go_router/test_fixes directory.
|
||||
```sh
|
||||
dart fix --compare-to-golden
|
||||
```
|
||||
|
||||
For more documentation about Data Driven Fixes, see
|
||||
https://dart.dev/go/data-driven-fixes#test-folder.
|
1
packages/go_router/test_fixes/analysis_options.yaml
Normal file
1
packages/go_router/test_fixes/analysis_options.yaml
Normal file
@ -0,0 +1 @@
|
||||
# This ensures that parent analysis options do not accidentally break the fix tests.
|
44
packages/go_router/test_fixes/go_router.dart
Normal file
44
packages/go_router/test_fixes/go_router.dart
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
const GoRouterState state = GoRouterState();
|
||||
final GoRouter router = GoRouter(routes: <RouteBase>[]);
|
||||
state.fullpath;
|
||||
state.params;
|
||||
state.subloc;
|
||||
state.queryParams;
|
||||
state.namedLocation(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
router.namedLocation(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
router.goNamed(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
router.pushNamed(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
router.pushReplacementNamed(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
router.replaceNamed(
|
||||
'name',
|
||||
params: <String, String>{},
|
||||
queryParams: <String, String>{},
|
||||
);
|
||||
}
|
44
packages/go_router/test_fixes/go_router.dart.expect
Normal file
44
packages/go_router/test_fixes/go_router.dart.expect
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
const GoRouterState state = GoRouterState();
|
||||
final GoRouter router = GoRouter(routes: <RouteBase>[]);
|
||||
state.fullPath;
|
||||
state.pathParameters;
|
||||
state.matchedLocation;
|
||||
state.queryParameters;
|
||||
state.namedLocation(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
router.namedLocation(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
router.goNamed(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
router.pushNamed(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
router.pushReplacementNamed(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
router.replaceNamed(
|
||||
'name',
|
||||
pathParameters: <String, String>{},
|
||||
queryParameters: <String, String>{},
|
||||
);
|
||||
}
|
51
packages/go_router/tool/run_tests.dart
Normal file
51
packages/go_router/tool/run_tests.dart
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Called from the custom-tests CI action.
|
||||
//
|
||||
// usage: dart run tool/run_tests.dart
|
||||
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
if (!Platform.isMacOS) {
|
||||
print('This test can only be run on macOS.');
|
||||
exit(0);
|
||||
}
|
||||
final Directory packageRoot =
|
||||
Directory(p.dirname(Platform.script.path)).parent;
|
||||
final int status = await _runProcess(
|
||||
'dart',
|
||||
<String>[
|
||||
'fix',
|
||||
'--compare-to-golden',
|
||||
],
|
||||
workingDirectory: p.join(packageRoot.path, 'test_fixes'),
|
||||
);
|
||||
|
||||
exit(status);
|
||||
}
|
||||
|
||||
Future<Process> _streamOutput(Future<Process> processFuture) async {
|
||||
final Process process = await processFuture;
|
||||
stdout.addStream(process.stdout);
|
||||
stderr.addStream(process.stderr);
|
||||
return process;
|
||||
}
|
||||
|
||||
Future<int> _runProcess(
|
||||
String command,
|
||||
List<String> arguments, {
|
||||
String? workingDirectory,
|
||||
}) async {
|
||||
final Process process = await _streamOutput(Process.start(
|
||||
command,
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
));
|
||||
return process.exitCode;
|
||||
}
|
@ -23,3 +23,5 @@
|
||||
- rfw/example
|
||||
# Disables docs requirements, as it is test code.
|
||||
- web_benchmarks/testing/test_app
|
||||
# Has some test files that are intentionally broken to conduct dart fix tests.
|
||||
- go_router
|
||||
|
Reference in New Issue
Block a user