[go_router] Fix assertion failure on URLs with hash fragments missing leading slash (#11431)

Fixes https://github.com/flutter/flutter/issues/184109
Fixes https://github.com/flutter/flutter/issues/181629
diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart
index e78011f..15c159a 100644
--- a/packages/go_router/lib/src/configuration.dart
+++ b/packages/go_router/lib/src/configuration.dart
@@ -260,12 +260,17 @@
 
   /// 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.
diff --git a/packages/go_router/pending_changelogs/fix_assertion_missing_slash.yaml b/packages/go_router/pending_changelogs/fix_assertion_missing_slash.yaml
new file mode 100644
index 0000000..378fbff
--- /dev/null
+++ b/packages/go_router/pending_changelogs/fix_assertion_missing_slash.yaml
@@ -0,0 +1,3 @@
+changelog: |
+  - Fixes an assertion failure when navigating to URLs with hash fragments missing a leading slash.
+version: patch
diff --git a/packages/go_router/test/configuration_test.dart b/packages/go_router/test/configuration_test.dart
index 88185f0..2e6e9cc 100644
--- a/packages/go_router/test/configuration_test.dart
+++ b/packages/go_router/test/configuration_test.dart
@@ -1010,6 +1010,54 @@
         );
       },
     );
+
+    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);
+      });
+    });
   });
 }
 
diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart
index 3859f27..d6d4d18 100644
--- a/packages/go_router/test/parser_test.dart
+++ b/packages/go_router/test/parser_test.dart
@@ -662,4 +662,30 @@
     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');
+    },
+  );
 }