// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

import 'test_helpers.dart';

void main() {
  group('RouteConfiguration', () {
    test('throws when parentNavigatorKey is not an ancestor', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final a = GlobalKey<NavigatorState>(debugLabel: 'a');
      final b = GlobalKey<NavigatorState>(debugLabel: 'b');

      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            GoRoute(
              path: '/a',
              builder: _mockScreenBuilder,
              routes: <RouteBase>[
                ShellRoute(
                  navigatorKey: a,
                  builder: _mockShellBuilder,
                  routes: <RouteBase>[
                    GoRoute(path: 'b', builder: _mockScreenBuilder),
                  ],
                ),
                ShellRoute(
                  navigatorKey: b,
                  builder: _mockShellBuilder,
                  routes: <RouteBase>[
                    GoRoute(
                      path: 'c',
                      parentNavigatorKey: a,
                      builder: _mockScreenBuilder,
                    ),
                  ],
                ),
              ],
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsAssertionError);
    });

    test('throws when ShellRoute has no children', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final shellRouteChildren = <RouteBase>[];
      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[ShellRoute(routes: shellRouteChildren)],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsAssertionError);
    });

    test(
      'throws when StatefulShellRoute sub-route uses incorrect parentNavigatorKey',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final keyA = GlobalKey<NavigatorState>(debugLabel: 'A');
        final keyB = GlobalKey<NavigatorState>(debugLabel: 'B');

        expect(() {
          createRouteConfiguration(
            navigatorKey: root,
            routes: <RouteBase>[
              StatefulShellRoute.indexedStack(
                branches: <StatefulShellBranch>[
                  StatefulShellBranch(
                    navigatorKey: keyA,
                    routes: <RouteBase>[
                      GoRoute(
                        path: '/a',
                        builder: _mockScreenBuilder,
                        routes: <RouteBase>[
                          GoRoute(
                            path: 'details',
                            builder: _mockScreenBuilder,
                            parentNavigatorKey: keyB,
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
                builder: mockStackedShellBuilder,
              ),
            ],
            redirectLimit: 10,
            topRedirect: (BuildContext context, GoRouterState state) {
              return null;
            },
          );
        }, throwsA(isA<AssertionError>()));
      },
    );

    test(
      'does not throw when StatefulShellRoute sub-route uses correct parentNavigatorKeys',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final keyA = GlobalKey<NavigatorState>(debugLabel: 'A');

        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            StatefulShellRoute.indexedStack(
              branches: <StatefulShellBranch>[
                StatefulShellBranch(
                  navigatorKey: keyA,
                  routes: <RouteBase>[
                    GoRoute(
                      path: '/a',
                      builder: _mockScreenBuilder,
                      routes: <RouteBase>[
                        GoRoute(
                          path: 'details',
                          builder: _mockScreenBuilder,
                          parentNavigatorKey: keyA,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
              builder: mockStackedShellBuilder,
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      },
    );

    test(
      'throws when a sub-route of StatefulShellRoute has a parentNavigatorKey',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final someNavigatorKey = GlobalKey<NavigatorState>();
        expect(() {
          createRouteConfiguration(
            navigatorKey: root,
            routes: <RouteBase>[
              StatefulShellRoute.indexedStack(
                branches: <StatefulShellBranch>[
                  StatefulShellBranch(
                    routes: <RouteBase>[
                      GoRoute(
                        path: '/a',
                        builder: _mockScreenBuilder,
                        routes: <RouteBase>[
                          GoRoute(
                            path: 'details',
                            builder: _mockScreenBuilder,
                            parentNavigatorKey: someNavigatorKey,
                          ),
                        ],
                      ),
                    ],
                  ),
                  StatefulShellBranch(
                    routes: <RouteBase>[
                      GoRoute(
                        path: '/b',
                        builder: _mockScreenBuilder,
                        parentNavigatorKey: someNavigatorKey,
                      ),
                    ],
                  ),
                ],
                builder: mockStackedShellBuilder,
              ),
            ],
            redirectLimit: 10,
            topRedirect: (BuildContext context, GoRouterState state) {
              return null;
            },
          );
        }, throwsAssertionError);
      },
    );

    test('throws when StatefulShellRoute has duplicate navigator keys', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final keyA = GlobalKey<NavigatorState>(debugLabel: 'A');
      final shellRouteChildren = <GoRoute>[
        GoRoute(
          path: '/a',
          builder: _mockScreenBuilder,
          parentNavigatorKey: keyA,
        ),
        GoRoute(
          path: '/b',
          builder: _mockScreenBuilder,
          parentNavigatorKey: keyA,
        ),
      ];
      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            StatefulShellRoute.indexedStack(
              branches: <StatefulShellBranch>[
                StatefulShellBranch(routes: shellRouteChildren),
              ],
              builder: mockStackedShellBuilder,
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsAssertionError);
    });

    test('throws when a child of StatefulShellRoute has an incorrect '
        'parentNavigatorKey', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final sectionANavigatorKey = GlobalKey<NavigatorState>();
      final sectionBNavigatorKey = GlobalKey<NavigatorState>();
      final routeA = GoRoute(
        path: '/a',
        builder: _mockScreenBuilder,
        parentNavigatorKey: sectionBNavigatorKey,
      );
      final routeB = GoRoute(
        path: '/b',
        builder: _mockScreenBuilder,
        parentNavigatorKey: sectionANavigatorKey,
      );
      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            StatefulShellRoute.indexedStack(
              branches: <StatefulShellBranch>[
                StatefulShellBranch(
                  routes: <RouteBase>[routeA],
                  navigatorKey: sectionANavigatorKey,
                ),
                StatefulShellBranch(
                  routes: <RouteBase>[routeB],
                  navigatorKey: sectionBNavigatorKey,
                ),
              ],
              builder: mockStackedShellBuilder,
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsAssertionError);
    });

    test('throws when a branch of a StatefulShellRoute has an incorrect '
        'initialLocation', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final sectionANavigatorKey = GlobalKey<NavigatorState>();
      final sectionBNavigatorKey = GlobalKey<NavigatorState>();
      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            StatefulShellRoute.indexedStack(
              branches: <StatefulShellBranch>[
                StatefulShellBranch(
                  initialLocation: '/x',
                  navigatorKey: sectionANavigatorKey,
                  routes: <RouteBase>[
                    GoRoute(path: '/a', builder: _mockScreenBuilder),
                  ],
                ),
                StatefulShellBranch(
                  navigatorKey: sectionBNavigatorKey,
                  routes: <RouteBase>[
                    GoRoute(path: '/b', builder: _mockScreenBuilder),
                  ],
                ),
              ],
              builder: mockStackedShellBuilder,
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsA(isA<AssertionError>()));
    });

    test('throws when a branch of a StatefulShellRoute has a initialLocation '
        'that is not a descendant of the same branch', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final sectionANavigatorKey = GlobalKey<NavigatorState>();
      final sectionBNavigatorKey = GlobalKey<NavigatorState>();
      expect(() {
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            StatefulShellRoute.indexedStack(
              branches: <StatefulShellBranch>[
                StatefulShellBranch(
                  initialLocation: '/b',
                  navigatorKey: sectionANavigatorKey,
                  routes: <RouteBase>[
                    GoRoute(path: '/a', builder: _mockScreenBuilder),
                  ],
                ),
                StatefulShellBranch(
                  initialLocation: '/b',
                  navigatorKey: sectionBNavigatorKey,
                  routes: <RouteBase>[
                    StatefulShellRoute.indexedStack(
                      branches: <StatefulShellBranch>[
                        StatefulShellBranch(
                          routes: <RouteBase>[
                            GoRoute(path: '/b', builder: _mockScreenBuilder),
                          ],
                        ),
                      ],
                      builder: mockStackedShellBuilder,
                    ),
                  ],
                ),
              ],
              builder: mockStackedShellBuilder,
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      }, throwsA(isA<AssertionError>()));
    });

    test('does not throw when a branch of a StatefulShellRoute has correctly '
        'configured initialLocations', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');

      createRouteConfiguration(
        navigatorKey: root,
        routes: <RouteBase>[
          StatefulShellRoute.indexedStack(
            branches: <StatefulShellBranch>[
              StatefulShellBranch(
                routes: <RouteBase>[
                  GoRoute(
                    path: '/a',
                    builder: _mockScreenBuilder,
                    routes: <RouteBase>[
                      GoRoute(path: 'detail', builder: _mockScreenBuilder),
                    ],
                  ),
                ],
              ),
              StatefulShellBranch(
                initialLocation: '/b/detail',
                routes: <RouteBase>[
                  GoRoute(
                    path: '/b',
                    builder: _mockScreenBuilder,
                    routes: <RouteBase>[
                      GoRoute(path: 'detail', builder: _mockScreenBuilder),
                    ],
                  ),
                ],
              ),
              StatefulShellBranch(
                initialLocation: '/c/detail',
                routes: <RouteBase>[
                  StatefulShellRoute.indexedStack(
                    branches: <StatefulShellBranch>[
                      StatefulShellBranch(
                        routes: <RouteBase>[
                          GoRoute(
                            path: '/c',
                            builder: _mockScreenBuilder,
                            routes: <RouteBase>[
                              GoRoute(
                                path: 'detail',
                                builder: _mockScreenBuilder,
                              ),
                            ],
                          ),
                        ],
                      ),
                      StatefulShellBranch(
                        initialLocation: '/d/detail',
                        routes: <RouteBase>[
                          GoRoute(
                            path: '/d',
                            builder: _mockScreenBuilder,
                            routes: <RouteBase>[
                              GoRoute(
                                path: 'detail',
                                builder: _mockScreenBuilder,
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                    builder: mockStackedShellBuilder,
                  ),
                ],
              ),
              StatefulShellBranch(
                routes: <RouteBase>[
                  ShellRoute(
                    builder: _mockShellBuilder,
                    routes: <RouteBase>[
                      ShellRoute(
                        builder: _mockShellBuilder,
                        routes: <RouteBase>[
                          GoRoute(path: '/e', builder: _mockScreenBuilder),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
            builder: mockStackedShellBuilder,
          ),
        ],
        redirectLimit: 10,
        topRedirect: (BuildContext context, GoRouterState state) {
          return null;
        },
      );
    });

    test('derives the correct initialLocation for a StatefulShellBranch', () {
      final StatefulShellBranch branchA;
      final StatefulShellBranch branchY;
      final StatefulShellBranch branchB;

      final RouteConfiguration config = createRouteConfiguration(
        navigatorKey: GlobalKey<NavigatorState>(debugLabel: 'root'),
        routes: <RouteBase>[
          StatefulShellRoute.indexedStack(
            builder: mockStackedShellBuilder,
            branches: <StatefulShellBranch>[
              branchA = StatefulShellBranch(
                routes: <RouteBase>[
                  GoRoute(
                    path: '/a',
                    builder: _mockScreenBuilder,
                    routes: <RouteBase>[
                      GoRoute(
                        path: 'x',
                        builder: _mockScreenBuilder,
                        routes: <RouteBase>[
                          StatefulShellRoute.indexedStack(
                            builder: mockStackedShellBuilder,
                            branches: <StatefulShellBranch>[
                              branchY = StatefulShellBranch(
                                routes: <RouteBase>[
                                  ShellRoute(
                                    builder: _mockShellBuilder,
                                    routes: <RouteBase>[
                                      GoRoute(
                                        path: 'y1',
                                        builder: _mockScreenBuilder,
                                      ),
                                      GoRoute(
                                        path: 'y2',
                                        builder: _mockScreenBuilder,
                                      ),
                                    ],
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
              branchB = StatefulShellBranch(
                routes: <RouteBase>[
                  ShellRoute(
                    builder: _mockShellBuilder,
                    routes: <RouteBase>[
                      ShellRoute(
                        builder: _mockShellBuilder,
                        routes: <RouteBase>[
                          GoRoute(path: '/b1', builder: _mockScreenBuilder),
                          GoRoute(path: '/b2', builder: _mockScreenBuilder),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
        redirectLimit: 10,
        topRedirect: (BuildContext context, GoRouterState state) {
          return null;
        },
      );

      String? initialLocation(StatefulShellBranch branch) {
        final GoRoute? route = branch.defaultRoute;
        return route != null ? config.locationForRoute(route) : null;
      }

      expect('/a', initialLocation(branchA));
      expect('/a/x/y1', initialLocation(branchY));
      expect('/b1', initialLocation(branchB));
    });

    test(
      'throws when there is a GoRoute ancestor with a different parentNavigatorKey',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
        expect(() {
          createRouteConfiguration(
            navigatorKey: root,
            routes: <RouteBase>[
              ShellRoute(
                navigatorKey: shell,
                routes: <RouteBase>[
                  GoRoute(
                    path: '/',
                    builder: _mockScreenBuilder,
                    parentNavigatorKey: root,
                    routes: <RouteBase>[
                      GoRoute(
                        path: 'a',
                        builder: _mockScreenBuilder,
                        parentNavigatorKey: shell,
                      ),
                    ],
                  ),
                ],
              ),
            ],
            redirectLimit: 10,
            topRedirect: (BuildContext context, GoRouterState state) {
              return null;
            },
          );
        }, throwsAssertionError);
      },
    );

    test('Does not throw with valid parentNavigatorKey configuration', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
      final shell2 = GlobalKey<NavigatorState>(debugLabel: 'shell2');
      createRouteConfiguration(
        navigatorKey: root,
        routes: <RouteBase>[
          ShellRoute(
            navigatorKey: shell,
            routes: <RouteBase>[
              GoRoute(
                path: '/',
                builder: _mockScreenBuilder,
                routes: <RouteBase>[
                  GoRoute(
                    path: 'a',
                    builder: _mockScreenBuilder,
                    parentNavigatorKey: root,
                    routes: <RouteBase>[
                      ShellRoute(
                        navigatorKey: shell2,
                        routes: <RouteBase>[
                          GoRoute(
                            path: 'b',
                            builder: _mockScreenBuilder,
                            routes: <RouteBase>[
                              GoRoute(
                                path: 'b',
                                builder: _mockScreenBuilder,
                                parentNavigatorKey: shell2,
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
        redirectLimit: 10,
        topRedirect: (BuildContext context, GoRouterState state) {
          return null;
        },
      );
    });

    test(
      'Does not throw with multiple nested GoRoutes using parentNavigatorKey in ShellRoute',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            ShellRoute(
              navigatorKey: shell,
              routes: <RouteBase>[
                GoRoute(
                  path: '/',
                  builder: _mockScreenBuilder,
                  routes: <RouteBase>[
                    GoRoute(
                      path: 'a',
                      builder: _mockScreenBuilder,
                      parentNavigatorKey: root,
                      routes: <RouteBase>[
                        GoRoute(
                          path: 'b',
                          builder: _mockScreenBuilder,
                          parentNavigatorKey: root,
                          routes: <RouteBase>[
                            GoRoute(
                              path: 'c',
                              builder: _mockScreenBuilder,
                              parentNavigatorKey: root,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      },
    );

    test('Throws when parentNavigatorKeys are overlapping', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
      expect(
        () => createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            ShellRoute(
              navigatorKey: shell,
              routes: <RouteBase>[
                GoRoute(
                  path: '/',
                  builder: _mockScreenBuilder,
                  routes: <RouteBase>[
                    GoRoute(
                      path: 'a',
                      builder: _mockScreenBuilder,
                      parentNavigatorKey: root,
                      routes: <RouteBase>[
                        GoRoute(
                          path: 'b',
                          builder: _mockScreenBuilder,
                          routes: <RouteBase>[
                            GoRoute(
                              path: 'b',
                              builder: _mockScreenBuilder,
                              parentNavigatorKey: shell,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        ),
        throwsA(isA<AssertionError>()),
      );
    });

    test(
      'Does not throw when parentNavigatorKeys are overlapping correctly',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
        createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            ShellRoute(
              navigatorKey: shell,
              routes: <RouteBase>[
                GoRoute(
                  path: '/',
                  builder: _mockScreenBuilder,
                  routes: <RouteBase>[
                    GoRoute(
                      path: 'a',
                      builder: _mockScreenBuilder,
                      parentNavigatorKey: shell,
                      routes: <RouteBase>[
                        GoRoute(
                          path: 'b',
                          builder: _mockScreenBuilder,
                          routes: <RouteBase>[
                            GoRoute(
                              path: 'b',
                              builder: _mockScreenBuilder,
                              parentNavigatorKey: root,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        );
      },
    );

    test('throws when a GoRoute with a different parentNavigatorKey '
        'exists between a GoRoute with a parentNavigatorKey and '
        'its ShellRoute ancestor', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
      final shell2 = GlobalKey<NavigatorState>(debugLabel: 'shell2');
      expect(
        () => createRouteConfiguration(
          navigatorKey: root,
          routes: <RouteBase>[
            ShellRoute(
              navigatorKey: shell,
              routes: <RouteBase>[
                GoRoute(
                  path: '/',
                  builder: _mockScreenBuilder,
                  routes: <RouteBase>[
                    GoRoute(
                      path: 'a',
                      parentNavigatorKey: root,
                      builder: _mockScreenBuilder,
                      routes: <RouteBase>[
                        ShellRoute(
                          navigatorKey: shell2,
                          routes: <RouteBase>[
                            GoRoute(
                              path: 'b',
                              builder: _mockScreenBuilder,
                              routes: <RouteBase>[
                                GoRoute(
                                  path: 'c',
                                  builder: _mockScreenBuilder,
                                  parentNavigatorKey: shell,
                                ),
                              ],
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
        ),
        throwsA(isA<AssertionError>()),
      );
    });
    test(
      'does not throw when ShellRoute is the child of another ShellRoute',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        createRouteConfiguration(
          routes: <RouteBase>[
            ShellRoute(
              builder: _mockShellBuilder,
              routes: <RouteBase>[
                ShellRoute(
                  builder: _mockShellBuilder,
                  routes: <GoRoute>[
                    GoRoute(path: '/a', builder: _mockScreenBuilder),
                  ],
                ),
                GoRoute(path: '/b', builder: _mockScreenBuilder),
              ],
            ),
            GoRoute(path: '/c', builder: _mockScreenBuilder),
          ],
          redirectLimit: 10,
          topRedirect: (BuildContext context, GoRouterState state) {
            return null;
          },
          navigatorKey: root,
        );
      },
    );

    test('Does not throw with valid parentNavigatorKey configuration', () {
      final root = GlobalKey<NavigatorState>(debugLabel: 'root');
      final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');
      final shell2 = GlobalKey<NavigatorState>(debugLabel: 'shell2');
      createRouteConfiguration(
        navigatorKey: root,
        routes: <RouteBase>[
          ShellRoute(
            navigatorKey: shell,
            routes: <RouteBase>[
              GoRoute(
                path: '/',
                builder: _mockScreenBuilder,
                routes: <RouteBase>[
                  GoRoute(
                    path: 'a',
                    builder: _mockScreenBuilder,
                    parentNavigatorKey: root,
                    routes: <RouteBase>[
                      ShellRoute(
                        navigatorKey: shell2,
                        routes: <RouteBase>[
                          GoRoute(
                            path: 'b',
                            builder: _mockScreenBuilder,
                            routes: <RouteBase>[
                              GoRoute(
                                path: 'b',
                                builder: _mockScreenBuilder,
                                parentNavigatorKey: shell2,
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
        redirectLimit: 10,
        topRedirect: (BuildContext context, GoRouterState state) {
          return null;
        },
      );
    });

    test(
      'throws when ShellRoute contains a GoRoute with a parentNavigatorKey',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        expect(() {
          createRouteConfiguration(
            navigatorKey: root,
            routes: <RouteBase>[
              ShellRoute(
                routes: <RouteBase>[
                  GoRoute(
                    path: '/a',
                    builder: _mockScreenBuilder,
                    parentNavigatorKey: root,
                  ),
                ],
              ),
            ],
            redirectLimit: 10,
            topRedirect: (BuildContext context, GoRouterState state) {
              return null;
            },
          );
        }, throwsAssertionError);
      },
    );

    test(
      'All known route strings returned by debugKnownRoutes are correct',
      () {
        final root = GlobalKey<NavigatorState>(debugLabel: 'root');
        final shell = GlobalKey<NavigatorState>(debugLabel: 'shell');

        expect(
          createRouteConfiguration(
            navigatorKey: root,
            routes: <RouteBase>[
              GoRoute(
                path: '/a',
                parentNavigatorKey: root,
                builder: _mockScreenBuilder,
                routes: <RouteBase>[
                  ShellRoute(
                    navigatorKey: shell,
                    builder: _mockShellBuilder,
                    routes: <RouteBase>[
                      GoRoute(
                        path: 'b',
                        parentNavigatorKey: shell,
                        builder: _mockScreenBuilder,
                      ),
                      GoRoute(
                        path: 'c',
                        parentNavigatorKey: shell,
                        builder: _mockScreenBuilder,
                      ),
                    ],
                  ),
                ],
              ),
              GoRoute(
                path: '/d',
                parentNavigatorKey: root,
                builder: _mockScreenBuilder,
                routes: <RouteBase>[
                  GoRoute(
                    path: 'e',
                    parentNavigatorKey: root,
                    builder: _mockScreenBuilder,
                    routes: <RouteBase>[
                      GoRoute(
                        path: 'f',
                        parentNavigatorKey: root,
                        builder: _mockScreenBuilder,
                      ),
                    ],
                  ),
                ],
              ),
              GoRoute(
                path: '/g',
                builder: _mockScreenBuilder,
                routes: <RouteBase>[
                  StatefulShellRoute.indexedStack(
                    builder: _mockIndexedStackShellBuilder,
                    branches: <StatefulShellBranch>[
                      StatefulShellBranch(
                        routes: <RouteBase>[
                          GoRoute(path: 'h', builder: _mockScreenBuilder),
                        ],
                      ),
                      StatefulShellBranch(
                        routes: <RouteBase>[
                          GoRoute(path: 'i', builder: _mockScreenBuilder),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
            redirectLimit: 10,
            topRedirect: (BuildContext context, GoRouterState state) {
              return null;
            },
          ).debugKnownRoutes(),
          'Full paths for routes:\n'
          '├─/a (Widget)\n'
          '│ └─ (ShellRoute)\n'
          '│   ├─/a/b (Widget)\n'
          '│   └─/a/c (Widget)\n'
          '├─/d (Widget)\n'
          '│ └─/d/e (Widget)\n'
          '│   └─/d/e/f (Widget)\n'
          '└─/g (Widget)\n'
          '  └─ (ShellRoute)\n'
          '    ├─/g/h (Widget)\n'
          '    └─/g/i (Widget)\n',
        );
      },
    );

    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);
      });
    });
  });
}

class _MockScreen extends StatelessWidget {
  const _MockScreen({super.key});

  @override
  Widget build(BuildContext context) => const Placeholder();
}

Widget _mockScreenBuilder(BuildContext context, GoRouterState state) =>
    _MockScreen(key: state.pageKey);

Widget _mockShellBuilder(
  BuildContext context,
  GoRouterState state,
  Widget child,
) => child;

Widget _mockIndexedStackShellBuilder(
  BuildContext context,
  GoRouterState state,
  StatefulNavigationShell shell,
) => shell;
