From 8ea0d295cb93830b4f93843272bd1a60fd11eca6 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 00:21:54 +0800 Subject: [PATCH 1/7] Add GoRoute.caseSensitive --- packages/go_router/lib/src/match.dart | 14 ++++++++++++-- packages/go_router/lib/src/route.dart | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index 696f9d4c50d..981a5b2df57 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -218,7 +218,17 @@ abstract class RouteMatchBase with Diagnosticable { final String newMatchedLocation = concatenatePaths(matchedLocation, pathLoc); final String newMatchedPath = concatenatePaths(matchedPath, route.path); - if (newMatchedLocation.toLowerCase() == uri.path.toLowerCase()) { + + final String caseSensitiveNewMatchedLocation; + final String caseSensitiveUriPath; + if (route.caseSensitive) { + caseSensitiveNewMatchedLocation = newMatchedLocation; + caseSensitiveUriPath = uri.path; + } else { + caseSensitiveNewMatchedLocation = newMatchedLocation.toLowerCase(); + caseSensitiveUriPath = uri.path.toLowerCase(); + } + if (caseSensitiveNewMatchedLocation == caseSensitiveUriPath) { // A complete match. pathParameters.addAll(currentPathParameter); @@ -232,7 +242,7 @@ abstract class RouteMatchBase with Diagnosticable { ], }; } - assert(uri.path.startsWith(newMatchedLocation)); + assert(caseSensitiveUriPath.startsWith(caseSensitiveNewMatchedLocation)); assert(remainingLocation.isNotEmpty); final String childRestLoc = uri.path.substring( diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 703e470e9a6..27ff17c644d 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -275,6 +275,7 @@ class GoRoute extends RouteBase { super.parentNavigatorKey, super.redirect, this.onExit, + this.caseSensitive = false, super.routes = const [], }) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'), assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'), @@ -437,6 +438,9 @@ class GoRoute extends RouteBase { /// ``` final ExitCallback? onExit; + /// Whether the path is case sensitive or not. + final bool caseSensitive; + // TODO(chunhtai): move all regex related help methods to path_utils.dart. /// Match this route against a location. RegExpMatch? matchPatternAsPrefix(String loc) { From cd1e07ff2b04c61fe777c5b04bda143f2b35585a Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 00:23:25 +0800 Subject: [PATCH 2/7] Add test --- packages/go_router/test/go_router_test.dart | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index d98f10711c5..1f6ec552c0f 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -780,6 +780,57 @@ void main() { expect(find.byType(FamilyScreen), findsOneWidget); }); + testWidgets('match path case sensitively', (WidgetTester tester) async { + final FlutterExceptionHandler? oldFlutterError = FlutterError.onError; + addTearDown(() => FlutterError.onError = oldFlutterError); + final List errors = []; + FlutterError.onError = (FlutterErrorDetails details) { + errors.add(details); + }; + final List routes = [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) => + const HomeScreen(), + ), + GoRoute( + path: '/family/:fid', + caseSensitive: true, + builder: (BuildContext context, GoRouterState state) => + FamilyScreen(state.pathParameters['fid']!), + ), + ]; + + final GoRouter router = await createRouter(routes, tester); + const String wrongLoc = '/FaMiLy/f2'; + + expect(errors, isEmpty); + router.go(wrongLoc); + await tester.pumpAndSettle(); + + expect(errors, hasLength(1)); + expect( + errors.single.exception, + isAssertionError, + reason: 'The path is case sensitive', + ); + + const String loc = '/family/f2'; + router.go(loc); + await tester.pumpAndSettle(); + final List matches = + router.routerDelegate.currentConfiguration.matches; + + expect( + router.routerDelegate.currentConfiguration.uri.toString(), + loc, + ); + + expect(matches, hasLength(1)); + expect(find.byType(FamilyScreen), findsOneWidget); + expect(errors, hasLength(1), reason: 'No new errors should be thrown'); + }); + testWidgets( 'If there is more than one route to match, use the first match.', (WidgetTester tester) async { From 64e46073deb1530d21dc7cf84271d2f319d4fffb Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 00:23:33 +0800 Subject: [PATCH 3/7] Update version --- packages/go_router/CHANGELOG.md | 4 ++++ packages/go_router/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 19a6cdf7061..fdd48769d88 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 14.9.0 + +- Adds `caseSensitive` parameter to `GoRouter` (default to `false`). + ## 14.8.1 - Secured canPop method for the lack of matches in routerDelegate's configuration. diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 86b0e9643fa..dc98043840e 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -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: 14.8.1 +version: 14.9.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 From 297bd2b6179c5dd99f56d35ca931b7a3b1f8d5d3 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 20:17:51 +0800 Subject: [PATCH 4/7] Make case sensitive true by default --- packages/go_router/lib/src/route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 27ff17c644d..11fb0d4328e 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -275,7 +275,7 @@ class GoRoute extends RouteBase { super.parentNavigatorKey, super.redirect, this.onExit, - this.caseSensitive = false, + this.caseSensitive = true, super.routes = const [], }) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'), assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'), From 98e7df4b95e32b6666a177f0adacd2eef7b4fb12 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 20:18:02 +0800 Subject: [PATCH 5/7] Update the documentation --- packages/go_router/CHANGELOG.md | 7 +++++-- packages/go_router/README.md | 1 + packages/go_router/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index fdd48769d88..2c80a43d6e1 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,6 +1,9 @@ -## 14.9.0 +## 15.0.0 -- Adds `caseSensitive` parameter to `GoRouter` (default to `false`). +- **BREAKING CHANGE** + - URLs are now case sensitive. + - Adds `caseSensitive` parameter to `GoRouter` (default to `true`). + - See [Migrating to 15.0.0](https://flutter.dev/go/go-router-v15-breaking-changes) ## 14.8.1 diff --git a/packages/go_router/README.md b/packages/go_router/README.md index 12da5815576..8f54d8e10e1 100644 --- a/packages/go_router/README.md +++ b/packages/go_router/README.md @@ -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 15.0.0](https://flutter.dev/go/go-router-v15-breaking-changes). - [Migrating to 14.0.0](https://flutter.dev/go/go-router-v14-breaking-changes). - [Migrating to 13.0.0](https://flutter.dev/go/go-router-v13-breaking-changes). - [Migrating to 12.0.0](https://flutter.dev/go/go-router-v12-breaking-changes). diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index dc98043840e..90bdc10f5e9 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -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: 14.9.0 +version: 15.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 From 16cadc5d724a7a1e9cb4f891763f71fa2ae996cc Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 4 Apr 2025 20:18:15 +0800 Subject: [PATCH 6/7] Update the tests --- packages/go_router/test/go_router_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index 1f6ec552c0f..80e170b7924 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -755,6 +755,7 @@ void main() { ), GoRoute( path: '/family/:fid', + caseSensitive: false, builder: (BuildContext context, GoRouterState state) => FamilyScreen(state.pathParameters['fid']!), ), @@ -795,7 +796,6 @@ void main() { ), GoRoute( path: '/family/:fid', - caseSensitive: true, builder: (BuildContext context, GoRouterState state) => FamilyScreen(state.pathParameters['fid']!), ), From 12f013608a088d1ae455a222872f99e69e583d7d Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Tue, 15 Apr 2025 17:51:50 +0800 Subject: [PATCH 7/7] Add better name and documentation --- packages/go_router/lib/src/match.dart | 16 ++++++++-------- packages/go_router/lib/src/route.dart | 10 +++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index 981a5b2df57..051c8b74960 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -219,16 +219,16 @@ abstract class RouteMatchBase with Diagnosticable { concatenatePaths(matchedLocation, pathLoc); final String newMatchedPath = concatenatePaths(matchedPath, route.path); - final String caseSensitiveNewMatchedLocation; - final String caseSensitiveUriPath; + final String newMatchedLocationToCompare; + final String uriPathToCompare; if (route.caseSensitive) { - caseSensitiveNewMatchedLocation = newMatchedLocation; - caseSensitiveUriPath = uri.path; + newMatchedLocationToCompare = newMatchedLocation; + uriPathToCompare = uri.path; } else { - caseSensitiveNewMatchedLocation = newMatchedLocation.toLowerCase(); - caseSensitiveUriPath = uri.path.toLowerCase(); + newMatchedLocationToCompare = newMatchedLocation.toLowerCase(); + uriPathToCompare = uri.path.toLowerCase(); } - if (caseSensitiveNewMatchedLocation == caseSensitiveUriPath) { + if (newMatchedLocationToCompare == uriPathToCompare) { // A complete match. pathParameters.addAll(currentPathParameter); @@ -242,7 +242,7 @@ abstract class RouteMatchBase with Diagnosticable { ], }; } - assert(caseSensitiveUriPath.startsWith(caseSensitiveNewMatchedLocation)); + assert(uriPathToCompare.startsWith(newMatchedLocationToCompare)); assert(remainingLocation.isNotEmpty); final String childRestLoc = uri.path.substring( diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 11fb0d4328e..d16e1dfd658 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -438,7 +438,15 @@ class GoRoute extends RouteBase { /// ``` final ExitCallback? onExit; - /// Whether the path is case sensitive or not. + /// Determines whether the route matching is case sensitive. + /// + /// When `true`, the path must match the specified case. For example, + /// a [GoRoute] with `path: '/family/:fid'` will not match `/FaMiLy/f2`. + /// + /// When `false`, the path matching is case insensitive. The route + /// with `path: '/family/:fid'` will match `/FaMiLy/f2`. + /// + /// Defaults to `true`. final bool caseSensitive; // TODO(chunhtai): move all regex related help methods to path_utils.dart.