Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,17 @@ class RouteConfiguration {

/// Normalizes a URI by ensuring it has a valid path and removing trailing slashes.
static Uri normalizeUri(Uri uri) {
if (uri.hasEmptyPath) {
return uri.replace(path: '/');
} else if (uri.path.length > 1 && uri.path.endsWith('/')) {
return uri.replace(path: uri.path.substring(0, uri.path.length - 1));
String path = uri.path;
if (!path.startsWith('/')) {
path = '/$path';
}
return uri;
if (path.length > 1 && path.endsWith('/')) {
path = path.substring(0, path.length - 1);
}
if (path == uri.path) {
return uri;
}
return uri.replace(path: path);
}

/// The global key for top level navigator.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Fixes an assertion failure when navigating to URLs with hash fragments missing a leading slash.
version: patch
48 changes: 48 additions & 0 deletions packages/go_router/test/configuration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,54 @@ void main() {
);
},
);

group('normalizeUri', () {
test('adds leading slash if missing', () {
expect(RouteConfiguration.normalizeUri(Uri.parse('foo')).path, '/foo');
});

test('handles empty path', () {
expect(RouteConfiguration.normalizeUri(Uri.parse('')).path, '/');
});

test('removes trailing slash if length > 1', () {
expect(
RouteConfiguration.normalizeUri(Uri.parse('/foo/')).path,
'/foo',
);
});

test('does not remove slash for root root', () {
expect(RouteConfiguration.normalizeUri(Uri.parse('/')).path, '/');
});

test('preserves query parameters and fragments', () {
final Uri uri = RouteConfiguration.normalizeUri(Uri.parse('foo?a=b#c'));
expect(uri.path, '/foo');
expect(uri.queryParameters['a'], 'b');
expect(uri.fragment, 'c');
});

test('handles hash fragments with authority', () {
final Uri uri = RouteConfiguration.normalizeUri(
Uri.parse('http://localhost:3000/#foo'),
);
expect(uri.path, '/');
expect(uri.fragment, 'foo');
});

test('handles hash fragments without authority', () {
final Uri uri = RouteConfiguration.normalizeUri(Uri.parse('/#foo'));
expect(uri.path, '/');
expect(uri.fragment, 'foo');
});

test('returns same instance if already normalized', () {
final Uri uri = Uri.parse('/foo');
final Uri normalized = RouteConfiguration.normalizeUri(uri);
expect(identical(uri, normalized), isTrue);
});
});
});
}

Expand Down
29 changes: 29 additions & 0 deletions packages/go_router/test/parser_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -662,4 +662,33 @@ void main() {
expect(match.matches, hasLength(1));
expect(matchesObj.error, isNull);
});

testWidgets(
'GoRouteInformationParser can handle path without leading slash',
(WidgetTester tester) async {
final routes = <RouteBase>[
GoRoute(
path: '/abc',
builder: (_, __) => const Placeholder(),
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);

final BuildContext context = tester.element(find.byType(Router<Object>));

final RouteMatchList matchesObj = await parser
.parseRouteInformationWithDependencies(
createRouteInformation('abc'),
context,
);
final List<RouteMatchBase> matches = matchesObj.matches;
expect(matches.length, 1);
expect(matchesObj.uri.toString(), '/abc');
},
);
}
Loading