Skip to content

Commit 4a2091d

Browse files
authored
[go_router] Fix assertion failure on URLs with hash fragments missing leading slash (#11431)
Fixes flutter/flutter#184109 Fixes flutter/flutter#181629
1 parent 9391572 commit 4a2091d

4 files changed

Lines changed: 87 additions & 5 deletions

File tree

packages/go_router/lib/src/configuration.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,17 @@ class RouteConfiguration {
260260

261261
/// Normalizes a URI by ensuring it has a valid path and removing trailing slashes.
262262
static Uri normalizeUri(Uri uri) {
263-
if (uri.hasEmptyPath) {
264-
return uri.replace(path: '/');
265-
} else if (uri.path.length > 1 && uri.path.endsWith('/')) {
266-
return uri.replace(path: uri.path.substring(0, uri.path.length - 1));
263+
String path = uri.path;
264+
if (!path.startsWith('/')) {
265+
path = '/$path';
267266
}
268-
return uri;
267+
if (path.length > 1 && path.endsWith('/')) {
268+
path = path.substring(0, path.length - 1);
269+
}
270+
if (path == uri.path) {
271+
return uri;
272+
}
273+
return uri.replace(path: path);
269274
}
270275

271276
/// The global key for top level navigator.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
changelog: |
2+
- Fixes an assertion failure when navigating to URLs with hash fragments missing a leading slash.
3+
version: patch

packages/go_router/test/configuration_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,54 @@ void main() {
10101010
);
10111011
},
10121012
);
1013+
1014+
group('normalizeUri', () {
1015+
test('adds leading slash if missing', () {
1016+
expect(RouteConfiguration.normalizeUri(Uri.parse('foo')).path, '/foo');
1017+
});
1018+
1019+
test('handles empty path', () {
1020+
expect(RouteConfiguration.normalizeUri(Uri.parse('')).path, '/');
1021+
});
1022+
1023+
test('removes trailing slash if length > 1', () {
1024+
expect(
1025+
RouteConfiguration.normalizeUri(Uri.parse('/foo/')).path,
1026+
'/foo',
1027+
);
1028+
});
1029+
1030+
test('does not remove slash for root root', () {
1031+
expect(RouteConfiguration.normalizeUri(Uri.parse('/')).path, '/');
1032+
});
1033+
1034+
test('preserves query parameters and fragments', () {
1035+
final Uri uri = RouteConfiguration.normalizeUri(Uri.parse('foo?a=b#c'));
1036+
expect(uri.path, '/foo');
1037+
expect(uri.queryParameters['a'], 'b');
1038+
expect(uri.fragment, 'c');
1039+
});
1040+
1041+
test('handles hash fragments with authority', () {
1042+
final Uri uri = RouteConfiguration.normalizeUri(
1043+
Uri.parse('http://localhost:3000/#foo'),
1044+
);
1045+
expect(uri.path, '/');
1046+
expect(uri.fragment, 'foo');
1047+
});
1048+
1049+
test('handles hash fragments without authority', () {
1050+
final Uri uri = RouteConfiguration.normalizeUri(Uri.parse('/#foo'));
1051+
expect(uri.path, '/');
1052+
expect(uri.fragment, 'foo');
1053+
});
1054+
1055+
test('returns same instance if already normalized', () {
1056+
final Uri uri = Uri.parse('/foo');
1057+
final Uri normalized = RouteConfiguration.normalizeUri(uri);
1058+
expect(identical(uri, normalized), isTrue);
1059+
});
1060+
});
10131061
});
10141062
}
10151063

packages/go_router/test/parser_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,4 +662,30 @@ void main() {
662662
expect(match.matches, hasLength(1));
663663
expect(matchesObj.error, isNull);
664664
});
665+
666+
testWidgets(
667+
'GoRouteInformationParser can handle path without leading slash',
668+
(WidgetTester tester) async {
669+
final routes = <RouteBase>[
670+
GoRoute(path: '/abc', builder: (_, __) => const Placeholder()),
671+
];
672+
final GoRouteInformationParser parser = await createParser(
673+
tester,
674+
routes: routes,
675+
redirectLimit: 100,
676+
redirect: (_, __) => null,
677+
);
678+
679+
final BuildContext context = tester.element(find.byType(Router<Object>));
680+
681+
final RouteMatchList matchesObj = await parser
682+
.parseRouteInformationWithDependencies(
683+
createRouteInformation('abc'),
684+
context,
685+
);
686+
final List<RouteMatchBase> matches = matchesObj.matches;
687+
expect(matches.length, 1);
688+
expect(matchesObj.uri.toString(), '/abc');
689+
},
690+
);
665691
}

0 commit comments

Comments
 (0)