diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index d268648720c..774d71c756e 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 13.2.3 + +- Fixes an issue where deep links without path caused an exception + ## 13.2.2 - Fixes restoreRouteInformation issue when GoRouter.optionURLReflectsImperativeAPIs is true and the last match is ShellRouteMatch diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 9cd92c1848b..bffcd1b15b6 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -79,11 +79,24 @@ class GoRouteInformationParser extends RouteInformationParser { } late final RouteMatchList initialMatches; - initialMatches = configuration.findMatch( - routeInformation.uri.path.isEmpty - ? '${routeInformation.uri}/' - : routeInformation.uri.toString(), - extra: state.extra); + if (routeInformation.uri.hasEmptyPath) { + String newUri = '${routeInformation.uri.origin}/'; + if (routeInformation.uri.hasQuery) { + newUri += '?${routeInformation.uri.query}'; + } + if (routeInformation.uri.hasFragment) { + newUri += '#${routeInformation.uri.fragment}'; + } + initialMatches = configuration.findMatch( + newUri, + extra: state.extra, + ); + } else { + initialMatches = configuration.findMatch( + routeInformation.uri.toString(), + extra: state.extra, + ); + } if (initialMatches.isError) { log('No initial matches: ${routeInformation.uri.path}'); } diff --git a/packages/go_router/lib/src/path_utils.dart b/packages/go_router/lib/src/path_utils.dart index 37e76853e31..49a7b33b3eb 100644 --- a/packages/go_router/lib/src/path_utils.dart +++ b/packages/go_router/lib/src/path_utils.dart @@ -126,19 +126,22 @@ String canonicalUri(String loc) { } String canon = Uri.parse(loc).toString(); canon = canon.endsWith('?') ? canon.substring(0, canon.length - 1) : canon; + final Uri uri = Uri.parse(canon); // remove trailing slash except for when you shouldn't, e.g. // /profile/ => /profile // / => / - // /login?from=/ => login?from=/ - canon = canon.endsWith('/') && canon != '/' && !canon.contains('?') + // /login?from=/ => /login?from=/ + canon = uri.path.endsWith('/') && + uri.path != '/' && + !uri.hasQuery && + !uri.hasFragment ? canon.substring(0, canon.length - 1) : canon; // replace '/?', except for first occurrence, from path only // /login/?from=/ => /login?from=/ // /?from=/ => /?from=/ - final Uri uri = Uri.parse(canon); final int pathStartIndex = uri.host.isNotEmpty ? uri.toString().indexOf(uri.host) + uri.host.length : uri.hasScheme diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index ac7945e62ec..1fc99858822 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: 13.2.2 +version: 13.2.3 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 diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart index 2d42de859e7..31b8a50e569 100644 --- a/packages/go_router/test/parser_test.dart +++ b/packages/go_router/test/parser_test.dart @@ -82,13 +82,54 @@ void main() { }); testWidgets( - "GoRouteInformationParser can parse deeplink route and maintain uri's scheme and host", + "GoRouteInformationParser can parse deeplink root route and maintain uri's scheme, host, query and fragment", + (WidgetTester tester) async { + const String expectedScheme = 'https'; + const String expectedHost = 'www.example.com'; + const String expectedQuery = 'abc=def'; + const String expectedFragment = 'abc'; + const String expectedUriString = + '$expectedScheme://$expectedHost/?$expectedQuery#$expectedFragment'; + final List routes = [ + GoRoute( + path: '/', + builder: (_, __) => const Placeholder(), + ), + ]; + final GoRouteInformationParser parser = await createParser( + tester, + routes: routes, + redirectLimit: 100, + redirect: (_, __) => null, + ); + + final BuildContext context = tester.element(find.byType(Router)); + + final RouteMatchList matchesObj = + await parser.parseRouteInformationWithDependencies( + createRouteInformation(expectedUriString), context); + final List matches = matchesObj.matches; + expect(matches.length, 1); + expect(matchesObj.uri.toString(), expectedUriString); + expect(matchesObj.uri.scheme, expectedScheme); + expect(matchesObj.uri.host, expectedHost); + expect(matchesObj.uri.query, expectedQuery); + expect(matchesObj.uri.fragment, expectedFragment); + + expect(matches[0].matchedLocation, '/'); + expect(matches[0].route, routes[0]); + }); + + testWidgets( + "GoRouteInformationParser can parse deeplink route with a path and maintain uri's scheme, host, query and fragment", (WidgetTester tester) async { const String expectedScheme = 'https'; const String expectedHost = 'www.example.com'; const String expectedPath = '/abc'; + const String expectedQuery = 'abc=def'; + const String expectedFragment = 'abc'; const String expectedUriString = - '$expectedScheme://$expectedHost$expectedPath'; + '$expectedScheme://$expectedHost$expectedPath?$expectedQuery#$expectedFragment'; final List routes = [ GoRoute( path: '/', @@ -119,6 +160,8 @@ void main() { expect(matchesObj.uri.scheme, expectedScheme); expect(matchesObj.uri.host, expectedHost); expect(matchesObj.uri.path, expectedPath); + expect(matchesObj.uri.query, expectedQuery); + expect(matchesObj.uri.fragment, expectedFragment); expect(matches[0].matchedLocation, '/'); expect(matches[0].route, routes[0]); diff --git a/packages/go_router/test/path_utils_test.dart b/packages/go_router/test/path_utils_test.dart index 1c883c45155..5a656df135e 100644 --- a/packages/go_router/test/path_utils_test.dart +++ b/packages/go_router/test/path_utils_test.dart @@ -100,6 +100,15 @@ void main() { verify('/a/', '/a'); verify('/', '/'); verify('/a/b/', '/a/b'); + verify('https://www.example.com/', 'https://www.example.com/'); + verify('https://www.example.com/a', 'https://www.example.com/a'); + verify('https://www.example.com/a/', 'https://www.example.com/a'); + verify('https://www.example.com/a/b/', 'https://www.example.com/a/b'); + verify('https://www.example.com/?', 'https://www.example.com/'); + verify('https://www.example.com/?a=b', 'https://www.example.com/?a=b'); + verify('https://www.example.com/?a=/', 'https://www.example.com/?a=/'); + verify('https://www.example.com/a/?b=c', 'https://www.example.com/a?b=c'); + verify('https://www.example.com/#a/', 'https://www.example.com/#a/'); expect(() => canonicalUri('::::'), throwsA(isA())); expect(() => canonicalUri(''), throwsA(anything));