import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'test_helpers.dart';
void main() {
group('RouteBuilder', () {
testWidgets('Builds GoRoute', (WidgetTester tester) async {
final RouteConfiguration config = createRouteConfiguration(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
navigatorKey: GlobalKey<NavigatorState>(),
);
final matches = RouteMatchList(
matches: <RouteMatch>[
RouteMatch(
route: config.routes.first as GoRoute,
matchedLocation: '/',
pageKey: const ValueKey<String>('/'),
),
],
uri: Uri.parse('/'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byType(_DetailsScreen), findsOneWidget);
});
testWidgets('Builds ShellRoute', (WidgetTester tester) async {
final shellNavigatorKey = GlobalKey<NavigatorState>();
final RouteConfiguration config = createRouteConfiguration(
routes: <RouteBase>[
ShellRoute(
navigatorKey: shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return _DetailsScreen();
},
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
navigatorKey: GlobalKey<NavigatorState>(),
);
final matches = RouteMatchList(
matches: <RouteMatchBase>[
ShellRouteMatch(
route: config.routes.first as ShellRouteBase,
matchedLocation: '',
pageKey: const ValueKey<String>(''),
navigatorKey: shellNavigatorKey,
matches: <RouteMatchBase>[
RouteMatch(
route: config.routes.first.routes.first as GoRoute,
matchedLocation: '/',
pageKey: const ValueKey<String>('/'),
),
],
),
],
uri: Uri.parse('/'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byType(_DetailsScreen), findsOneWidget);
});
testWidgets('Uses the correct navigatorKey', (WidgetTester tester) async {
final rootNavigatorKey = GlobalKey<NavigatorState>();
final RouteConfiguration config = createRouteConfiguration(
navigatorKey: rootNavigatorKey,
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
);
final matches = RouteMatchList(
matches: <RouteMatch>[
RouteMatch(
route: config.routes.first as GoRoute,
matchedLocation: '/',
pageKey: const ValueKey<String>('/'),
),
],
uri: Uri.parse('/'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byKey(rootNavigatorKey), findsOneWidget);
});
testWidgets('Builds a Navigator for ShellRoute', (
WidgetTester tester,
) async {
final rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell');
final RouteConfiguration config = createRouteConfiguration(
navigatorKey: rootNavigatorKey,
routes: <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return _HomeScreen(child: child);
},
navigatorKey: shellNavigatorKey,
routes: <RouteBase>[
GoRoute(
path: '/details',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
);
final matches = RouteMatchList(
matches: <RouteMatchBase>[
ShellRouteMatch(
route: config.routes.first as ShellRouteBase,
matchedLocation: '',
pageKey: const ValueKey<String>(''),
navigatorKey: shellNavigatorKey,
matches: <RouteMatchBase>[
RouteMatch(
route: config.routes.first.routes.first as GoRoute,
matchedLocation: '/details',
pageKey: const ValueKey<String>('/details'),
),
],
),
],
uri: Uri.parse('/details'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byType(_HomeScreen, skipOffstage: false), findsOneWidget);
expect(find.byType(_DetailsScreen), findsOneWidget);
expect(find.byKey(rootNavigatorKey), findsOneWidget);
expect(find.byKey(shellNavigatorKey), findsOneWidget);
});
testWidgets('Builds a Navigator for ShellRoute with parentNavigatorKey', (
WidgetTester tester,
) async {
final rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell');
final RouteConfiguration config = createRouteConfiguration(
navigatorKey: rootNavigatorKey,
routes: <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return _HomeScreen(child: child);
},
navigatorKey: shellNavigatorKey,
routes: <RouteBase>[
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
parentNavigatorKey: rootNavigatorKey,
),
],
),
],
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
);
final matches = RouteMatchList(
matches: <RouteMatch>[
RouteMatch(
route: config.routes.first.routes.first as GoRoute,
matchedLocation: '/a/details',
pageKey: const ValueKey<String>('/a/details'),
),
],
uri: Uri.parse('/a/details'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byType(_HomeScreen), findsNothing);
expect(find.byType(_DetailsScreen), findsOneWidget);
});
testWidgets('Uses the correct restorationScopeId for ShellRoute', (
WidgetTester tester,
) async {
final rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell');
final RouteConfiguration config = createRouteConfiguration(
navigatorKey: rootNavigatorKey,
routes: <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return _HomeScreen(child: child);
},
navigatorKey: shellNavigatorKey,
restorationScopeId: 'scope1',
routes: <RouteBase>[
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
),
],
redirectLimit: 10,
topRedirect: (BuildContext context, GoRouterState state) {
return null;
},
);
final matches = RouteMatchList(
matches: <RouteMatchBase>[
ShellRouteMatch(
route: config.routes.first as ShellRouteBase,
matchedLocation: '',
pageKey: const ValueKey<String>(''),
navigatorKey: shellNavigatorKey,
matches: <RouteMatchBase>[
RouteMatch(
route: config.routes.first.routes.first as GoRoute,
matchedLocation: '/a',
pageKey: const ValueKey<String>('/a'),
),
],
),
],
uri: Uri.parse('/b'),
pathParameters: const <String, String>{},
);
await tester.pumpWidget(
_BuilderTestWidget(routeConfiguration: config, matches: matches),
);
expect(find.byKey(rootNavigatorKey), findsOneWidget);
expect(find.byKey(shellNavigatorKey), findsOneWidget);
expect(
(shellNavigatorKey.currentWidget as Navigator?)?.restorationScopeId,
'scope1',
);
});
testWidgets('Uses the correct restorationScopeId for StatefulShellRoute', (
WidgetTester tester,
) async {
final rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final shellNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shell');
final goRouter = GoRouter(
initialLocation: '/a',
navigatorKey: rootNavigatorKey,
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
restorationScopeId: 'shell',
builder:
(
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) => _HomeScreen(child: navigationShell),
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: shellNavigatorKey,
restorationScopeId: 'scope1',
routes: <RouteBase>[
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return _DetailsScreen();
},
),
],
),
],
),
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(rootNavigatorKey), findsOneWidget);
expect(find.byKey(shellNavigatorKey), findsOneWidget);
expect(
(shellNavigatorKey.currentWidget as Navigator?)?.restorationScopeId,
'scope1',
);
});
testWidgets('GoRouter requestFocus defaults to true', (
WidgetTester tester,
) async {
final router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Scaffold(body: Center(child: Text('Home'))),
),
],
);
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
addTearDown(() => router.dispose());
final Navigator navigator = tester.widget<Navigator>(
find.byType(Navigator),
);
expect(navigator.requestFocus, isTrue);
});
testWidgets('GoRouter requestFocus can be set to false', (
WidgetTester tester,
) async {
final router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Scaffold(body: Center(child: Text('Home'))),
),
],
requestFocus: false,
);
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
addTearDown(() => router.dispose());
final Navigator navigator = tester.widget<Navigator>(
find.byType(Navigator),
);
expect(navigator.requestFocus, isFalse);
});
});
}
class _HomeScreen extends StatelessWidget {
const _HomeScreen({required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
const Text('Home Screen'),
Expanded(child: child),
],
),
);
}
}
class _DetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Scaffold(body: Text('Details Screen'));
}
}
class _BuilderTestWidget extends StatelessWidget {
_BuilderTestWidget({required this.routeConfiguration, required this.matches})
: builder = _routeBuilder(routeConfiguration);
final RouteConfiguration routeConfiguration;
final RouteBuilder builder;
final RouteMatchList matches;
static RouteBuilder _routeBuilder(RouteConfiguration configuration) {
return RouteBuilder(
configuration: configuration,
builderWithNav: (BuildContext context, Widget child) {
return child;
},
errorPageBuilder: (BuildContext context, GoRouterState state) {
return MaterialPage<dynamic>(child: Text('Error: ${state.error}'));
},
errorBuilder: (BuildContext context, GoRouterState state) {
return Text('Error: ${state.error}');
},
restorationScopeId: null,
observers: <NavigatorObserver>[],
onPopPageWithRouteMatch: (_, __, ___) => false,
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(home: builder.build(context, matches, false));
}
}