import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
class _GoRouteDataBuild extends GoRouteData {
const _GoRouteDataBuild();
@override
Widget build(BuildContext context, GoRouterState state) =>
const SizedBox(key: Key('build'));
}
class _ShellRouteDataRedirectPage extends ShellRouteData {
const _ShellRouteDataRedirectPage();
@override
FutureOr<String> redirect(BuildContext context, GoRouterState state) =>
'/build-page';
}
class _ShellRouteDataBuilder extends ShellRouteData {
const _ShellRouteDataBuilder();
@override
Widget builder(
BuildContext context,
GoRouterState state,
Widget navigator,
) =>
SizedBox(
key: const Key('builder'),
child: navigator,
);
}
class _ShellRouteDataWithKey extends ShellRouteData {
const _ShellRouteDataWithKey(this.key);
final Key key;
@override
Widget builder(
BuildContext context,
GoRouterState state,
Widget navigator,
) =>
KeyedSubtree(
key: key,
child: navigator,
);
}
class _GoRouteDataBuildWithKey extends GoRouteData {
const _GoRouteDataBuildWithKey(this.key);
final Key key;
@override
Widget build(BuildContext context, GoRouterState state) => SizedBox(key: key);
}
final GoRoute _goRouteDataBuild = GoRouteData.$route(
path: '/build',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
);
final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route(
factory: (GoRouterState state) => const _ShellRouteDataBuilder(),
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
);
class _GoRouteDataBuildPage extends GoRouteData {
const _GoRouteDataBuildPage();
@override
Page<void> buildPage(BuildContext context, GoRouterState state) =>
const MaterialPage<void>(
child: SizedBox(key: Key('buildPage')),
);
}
class _ShellRouteDataPageBuilder extends ShellRouteData {
const _ShellRouteDataPageBuilder();
@override
Page<void> pageBuilder(
BuildContext context,
GoRouterState state,
Widget navigator,
) =>
MaterialPage<void>(
child: SizedBox(
key: const Key('page-builder'),
child: navigator,
),
);
}
class _StatefulShellRouteDataRedirectPage extends StatefulShellRouteData {
const _StatefulShellRouteDataRedirectPage();
@override
FutureOr<String> redirect(BuildContext context, GoRouterState state) =>
'/build-page';
}
final GoRoute _goRouteDataBuildPage = GoRouteData.$route(
path: '/build-page',
factory: (GoRouterState state) => const _GoRouteDataBuildPage(),
);
final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route(
factory: (GoRouterState state) => const _ShellRouteDataPageBuilder(),
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
);
final ShellRoute _shellRouteDataRedirect = ShellRouteData.$route(
factory: (GoRouterState state) => const _ShellRouteDataPageBuilder(),
routes: <RouteBase>[
ShellRouteData.$route(
factory: (GoRouterState state) => const _ShellRouteDataRedirectPage(),
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
),
],
);
class _StatefulShellRouteDataBuilder extends StatefulShellRouteData {
const _StatefulShellRouteDataBuilder();
@override
Widget builder(
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigator,
) =>
SizedBox(
key: const Key('builder'),
child: navigator,
);
}
final StatefulShellRoute _statefulShellRouteDataBuilder =
StatefulShellRouteData.$route(
factory: (GoRouterState state) => const _StatefulShellRouteDataBuilder(),
branches: <StatefulShellBranch>[
StatefulShellBranchData.$branch(
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
),
],
);
class _StatefulShellRouteDataPageBuilder extends StatefulShellRouteData {
const _StatefulShellRouteDataPageBuilder();
@override
Page<void> pageBuilder(
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigator,
) =>
MaterialPage<void>(
child: SizedBox(
key: const Key('page-builder'),
child: navigator,
),
);
}
final StatefulShellRoute _statefulShellRouteDataPageBuilder =
StatefulShellRouteData.$route(
factory: (GoRouterState state) => const _StatefulShellRouteDataPageBuilder(),
branches: <StatefulShellBranch>[
StatefulShellBranchData.$branch(
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
),
],
);
class _GoRouteDataRedirectPage extends GoRouteData {
const _GoRouteDataRedirectPage();
@override
FutureOr<String> redirect(BuildContext context, GoRouterState state) =>
'/build-page';
}
final GoRoute _goRouteDataRedirect = GoRouteData.$route(
path: '/redirect',
factory: (GoRouterState state) => const _GoRouteDataRedirectPage(),
);
final List<GoRoute> _routes = <GoRoute>[
_goRouteDataBuild,
_goRouteDataBuildPage,
_goRouteDataRedirect,
];
void main() {
group('GoRouteData', () {
testWidgets(
'It should build the page from the overridden build method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/build',
routes: _routes,
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsOneWidget);
expect(find.byKey(const Key('buildPage')), findsNothing);
},
);
testWidgets(
'It should build the page from the overridden buildPage method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/build-page',
routes: _routes,
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsNothing);
expect(find.byKey(const Key('buildPage')), findsOneWidget);
},
);
testWidgets(
'It should build a go route with the default case sensitivity',
(WidgetTester tester) async {
final GoRoute routeWithDefaultCaseSensitivity = GoRouteData.$route(
path: '/path',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
);
expect(routeWithDefaultCaseSensitivity.caseSensitive, true);
},
);
testWidgets(
'It should build a go route with the overridden case sensitivity',
(WidgetTester tester) async {
final GoRoute routeWithDefaultCaseSensitivity = GoRouteData.$route(
path: '/path',
caseSensitive: false,
factory: (GoRouterState state) => const _GoRouteDataBuild(),
);
expect(routeWithDefaultCaseSensitivity.caseSensitive, false);
},
);
testWidgets(
'It should throw beacuase there is no code generated',
(WidgetTester tester) async {
final List<FlutterErrorDetails> errors = <FlutterErrorDetails>[];
FlutterError.onError =
(FlutterErrorDetails details) => errors.add(details);
const String errorText = 'Should be generated';
Widget buildWidget(void Function(BuildContext) onTap) {
return MaterialApp(
home: Builder(
builder: (BuildContext context) => GestureDetector(
child: const Text('Tap'),
onTap: () => onTap(context),
),
),
);
}
final Widget pushThrower = buildWidget((BuildContext context) {
const _GoRouteDataBuild().push<void>(context);
});
await tester.pumpWidget(pushThrower);
await tester.tap(find.text('Tap'));
expect(errors.first.exception, isA<UnimplementedError>());
expect(errors.first.exception.toString(), contains(errorText));
errors.clear();
final Widget goThrower = buildWidget((BuildContext context) {
const _GoRouteDataBuild().go(context);
});
await tester.pumpWidget(goThrower);
await tester.tap(find.text('Tap'));
expect(errors.first.exception, isA<UnimplementedError>());
expect(errors.first.exception.toString(), contains(errorText));
errors.clear();
final Widget pushReplacementThrower =
buildWidget((BuildContext context) {
const _GoRouteDataBuild().pushReplacement(context);
});
await tester.pumpWidget(pushReplacementThrower);
await tester.tap(find.text('Tap'));
expect(errors.first.exception, isA<UnimplementedError>());
expect(errors.first.exception.toString(), contains(errorText));
errors.clear();
final Widget replaceThrower = buildWidget((BuildContext context) {
const _GoRouteDataBuild().pushReplacement(context);
});
await tester.pumpWidget(replaceThrower);
await tester.tap(find.text('Tap'));
expect(errors.first.exception, isA<UnimplementedError>());
expect(errors.first.exception.toString(), contains(errorText));
errors.clear();
FlutterError.onError = FlutterError.dumpErrorToConsole;
},
);
});
group('ShellRouteData', () {
testWidgets(
'It should build the page from the overridden build method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_shellRouteDataBuilder,
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('builder')), findsOneWidget);
expect(find.byKey(const Key('page-builder')), findsNothing);
},
);
testWidgets(
'It should build the page from the overridden build method',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> root = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> inner = GlobalKey<NavigatorState>();
final GoRouter goRouter = GoRouter(
navigatorKey: root,
initialLocation: '/child/test',
routes: <RouteBase>[
ShellRouteData.$route(
factory: (GoRouterState state) =>
const _ShellRouteDataWithKey(Key('under-shell')),
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) =>
const _GoRouteDataBuildWithKey(Key('under')),
routes: <RouteBase>[
ShellRouteData.$route(
factory: (GoRouterState state) =>
const _ShellRouteDataWithKey(Key('above-shell')),
navigatorKey: inner,
parentNavigatorKey: root,
routes: <RouteBase>[
GoRouteData.$route(
parentNavigatorKey: inner,
path: 'test',
factory: (GoRouterState state) =>
const _GoRouteDataBuildWithKey(Key('above')),
),
],
),
]),
],
),
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(
routerConfig: goRouter,
));
expect(find.byKey(const Key('under-shell')), findsNothing);
expect(find.byKey(const Key('under')), findsNothing);
expect(find.byKey(const Key('above-shell')), findsOneWidget);
expect(find.byKey(const Key('above')), findsOneWidget);
goRouter.pop();
await tester.pumpAndSettle();
expect(find.byKey(const Key('under-shell')), findsOneWidget);
expect(find.byKey(const Key('under')), findsOneWidget);
expect(find.byKey(const Key('above-shell')), findsNothing);
expect(find.byKey(const Key('above')), findsNothing);
},
);
testWidgets(
'It should build the page from the overridden buildPage method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_shellRouteDataPageBuilder,
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('builder')), findsNothing);
expect(find.byKey(const Key('page-builder')), findsOneWidget);
},
);
testWidgets(
'It should redirect using the overridden redirect method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_goRouteDataBuildPage,
_shellRouteDataRedirect,
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsNothing);
expect(find.byKey(const Key('buildPage')), findsOneWidget);
},
);
});
group('StatefulShellRouteData', () {
testWidgets(
'It should build the page from the overridden build method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_statefulShellRouteDataBuilder,
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('builder')), findsOneWidget);
expect(find.byKey(const Key('page-builder')), findsNothing);
},
);
testWidgets(
'It should build the page from the overridden buildPage method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_statefulShellRouteDataPageBuilder,
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('builder')), findsNothing);
expect(find.byKey(const Key('page-builder')), findsOneWidget);
},
);
test('Can assign parent navigator key', () {
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
final StatefulShellRoute route = StatefulShellRouteData.$route(
parentNavigatorKey: key,
factory: (GoRouterState state) =>
const _StatefulShellRouteDataPageBuilder(),
branches: <StatefulShellBranch>[
StatefulShellBranchData.$branch(
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
),
],
);
expect(route.parentNavigatorKey, key);
});
});
group('StatefulShellBranchData', () {
test('Can assign preload', () {
final StatefulShellBranch branch = StatefulShellBranchData.$branch(
preload: true,
routes: <RouteBase>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
);
expect(branch.preload, true);
});
});
testWidgets(
'It should redirect using the overridden redirect method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/redirect',
routes: _routes,
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsNothing);
expect(find.byKey(const Key('buildPage')), findsOneWidget);
},
);
testWidgets(
'It should redirect using the overridden StatefulShellRoute redirect method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/child',
routes: <RouteBase>[
_goRouteDataBuildPage,
StatefulShellRouteData.$route(
factory: (GoRouterState state) =>
const _StatefulShellRouteDataRedirectPage(),
branches: <StatefulShellBranch>[
StatefulShellBranchData.$branch(
routes: <GoRoute>[
GoRouteData.$route(
path: '/child',
factory: (GoRouterState state) => const _GoRouteDataBuild(),
),
],
)
],
)
],
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsNothing);
expect(find.byKey(const Key('buildPage')), findsOneWidget);
},
);
testWidgets(
'It should redirect using the overridden redirect method',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/redirect-with-state',
routes: _routes,
);
addTearDown(goRouter.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter));
expect(find.byKey(const Key('build')), findsNothing);
expect(find.byKey(const Key('buildPage')), findsNothing);
},
);
test('TypedGoRoute with default parameters', () {
const TypedGoRoute<GoRouteData> typedGoRoute = TypedGoRoute<GoRouteData>(
path: '/path',
);
expect(typedGoRoute.path, '/path');
expect(typedGoRoute.name, isNull);
expect(typedGoRoute.caseSensitive, true);
expect(typedGoRoute.routes, isEmpty);
});
test('TypedGoRoute with provided parameters', () {
const TypedGoRoute<GoRouteData> typedGoRoute = TypedGoRoute<GoRouteData>(
path: '/path',
name: 'name',
caseSensitive: false,
routes: <TypedRoute<RouteData>>[
TypedGoRoute<GoRouteData>(
path: 'sub-path',
name: 'subName',
caseSensitive: false,
),
],
);
expect(typedGoRoute.path, '/path');
expect(typedGoRoute.name, 'name');
expect(typedGoRoute.caseSensitive, false);
expect(typedGoRoute.routes, hasLength(1));
expect(
typedGoRoute.routes.single,
isA<TypedGoRoute<GoRouteData>>()
.having((TypedGoRoute<GoRouteData> route) => route.path, 'path',
'sub-path')
.having(
(TypedGoRoute<GoRouteData> route) => route.name,
'name',
'subName',
)
.having(
(TypedGoRoute<GoRouteData> route) => route.caseSensitive,
'caseSensitive',
false,
),
);
});
}