diff --git a/go_router/example/.gitignore b/go_router/example/.gitignore index 0d920e61..a41e5f81 100644 --- a/go_router/example/.gitignore +++ b/go_router/example/.gitignore @@ -30,6 +30,7 @@ .pub-cache/ .pub/ build/ +coverage/ # Web related lib/generated_plugin_registrant.dart diff --git a/go_router/example/lib/redirection.dart b/go_router/example/lib/redirection.dart index 9aedc8e7..d4ad60fe 100644 --- a/go_router/example/lib/redirection.dart +++ b/go_router/example/lib/redirection.dart @@ -16,63 +16,69 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) => ChangeNotifierProvider.value( value: loginInfo, - child: MaterialApp.router( - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - debugShowCheckedModeBanner: false, - ), + child: Builder(builder: (context) { + final router = routerBuilder(context); + + return MaterialApp.router( + routeInformationParser: router.routeInformationParser, + routerDelegate: router.routerDelegate, + title: title, + debugShowCheckedModeBanner: false, + ); + }), ); +} - late final _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => HomeScreen(families: Families.data), - routes: [ - GoRoute( - path: 'family/:fid', - builder: (context, state) => FamilyScreen( - family: Families.family(state.params['fid']!), - ), - routes: [ - GoRoute( - path: 'person/:pid', - builder: (context, state) { - final family = Families.family(state.params['fid']!); - final person = family.person(state.params['pid']!); - return PersonScreen(family: family, person: person); - }, +GoRouter routerBuilder(BuildContext context, [String? location]) => GoRouter( + initialLocation: location ?? '/', + routes: [ + GoRoute( + path: '/', + builder: (context, state) => HomeScreen(families: Families.data), + routes: [ + GoRoute( + path: 'family/:fid', + builder: (context, state) => FamilyScreen( + family: Families.family(state.params['fid']!), ), - ], - ), - ], - ), - GoRoute( - path: '/login', - builder: (context, state) => const LoginScreen(), - ), - ], - - // redirect to the login page if the user is not logged in - redirect: (state) { - // if the user is not logged in, they need to login - final loggedIn = loginInfo.loggedIn; - final loggingIn = state.subloc == '/login'; - if (!loggedIn) return loggingIn ? null : '/login'; - - // if the user is logged in but still on the login page, send them to - // the home page - if (loggingIn) return '/'; - - // no need to redirect at all - return null; - }, - - // changes on the listenable will cause the router to refresh it's route - refreshListenable: loginInfo, - ); -} + routes: [ + GoRoute( + path: 'person/:pid', + builder: (context, state) { + final family = Families.family(state.params['fid']!); + final person = family.person(state.params['pid']!); + return PersonScreen(family: family, person: person); + }, + ), + ], + ), + ], + ), + GoRoute( + path: '/login', + builder: (context, state) => const LoginScreen(), + ), + ], + + // redirect to the login page if the user is not logged in + redirect: (state) { + final loginInfo = context.read(); + // if the user is not logged in, they need to login + final loggedIn = loginInfo.loggedIn; + final loggingIn = state.subloc == '/login'; + if (!loggedIn) return loggingIn ? null : '/login'; + + // if the user is logged in but still on the login page, send them to + // the home page + if (loggingIn) return '/'; + + // no need to redirect at all + return null; + }, + + // changes on the listenable will cause the router to refresh it's route + refreshListenable: context.read(), + ); class LoginScreen extends StatelessWidget { const LoginScreen({Key? key}) : super(key: key); diff --git a/go_router/example/pubspec.yaml b/go_router/example/pubspec.yaml index bb1ee245..ce277b3a 100644 --- a/go_router/example/pubspec.yaml +++ b/go_router/example/pubspec.yaml @@ -26,6 +26,7 @@ dev_dependencies: all_lint_rules_community: ^0.0.4 flutter_test: sdk: flutter + mocktail: ^0.2.0 flutter: uses-material-design: true diff --git a/go_router/example/test/helpers/mock_go_router.dart b/go_router/example/test/helpers/mock_go_router.dart new file mode 100644 index 00000000..f83d9195 --- /dev/null +++ b/go_router/example/test/helpers/mock_go_router.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:go_router/src/inherited_go_router.dart'; + +import 'package:mocktail/mocktail.dart'; + +class MockGoRouter extends Mock implements GoRouter {} + +/// {@template mock_navigator_provider} +/// The widget that provides an instance of a [MockGoRouter]. +/// {@endtemplate} +class MockGoRouterProvider extends StatelessWidget { + /// {@macro mock_navigator_provider} + const MockGoRouterProvider({ + required this.goRouter, + required this.child, + Key? key, + }) : super(key: key); + + /// The mock navigator used to mock navigation calls. + final GoRouter goRouter; + + /// The [Widget] to render. + final Widget child; + + @override + Widget build(BuildContext context) => InheritedGoRouter( + goRouter: goRouter, + child: child, + ); +} diff --git a/go_router/example/test/redirection_test.dart b/go_router/example/test/redirection_test.dart new file mode 100644 index 00000000..addf1028 --- /dev/null +++ b/go_router/example/test/redirection_test.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_examples/redirection.dart'; +import 'package:go_router_examples/shared/data.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:provider/provider.dart'; + +import 'helpers/mock_go_router.dart'; + +void main() { + late LoginInfo loginInfo; + setUp(() { + loginInfo = LoginInfo(); + }); + + testWidgets('should render the default page', (tester) async { + await tester.pumpWidget( + App(), + ); + + expect(find.byType(LoginScreen), findsOneWidget); + }); + + testWidgets('should redirect to home if logged in', (tester) async { + loginInfo.login('Username'); + + await tester.pumpWidget(ChangeNotifierProvider.value( + value: loginInfo, + child: Builder(builder: (context) { + final router = routerBuilder(context, '/login'); + + return MaterialApp.router( + routeInformationParser: router.routeInformationParser, + routerDelegate: router.routerDelegate, + debugShowCheckedModeBanner: false, + ); + }), + )); + + expect(find.byType(HomeScreen), findsOneWidget); + }); + + testWidgets('should redirect to family when clicking on tile', + (tester) async { + final loginInfo = LoginInfo()..login('Username'); + final mockGoRouter = MockGoRouter(); + + await tester.pumpWidget( + MaterialApp( + home: MockGoRouterProvider( + goRouter: mockGoRouter, + child: ChangeNotifierProvider.value( + value: loginInfo, + child: HomeScreen(families: Families.data), + ), + ), + ), + ); + + await tester.tap(find.byType(ListTile).first); + await tester.pumpAndSettle(); + + verify(() => mockGoRouter.go('/family/f1')).called(1); + verifyNever(() => mockGoRouter.go('/family/f2')); + }); + + testWidgets('should redirect to PersonScreen when clicking on tile', + (tester) async { + final loginInfo = LoginInfo()..login('Username'); + final mockGoRouter = MockGoRouter(); + + await tester.pumpWidget( + MaterialApp( + home: MockGoRouterProvider( + goRouter: mockGoRouter, + child: ChangeNotifierProvider.value( + value: loginInfo, + child: FamilyScreen(family: Families.data[0]), + ), + ), + ), + ); + + await tester.tap(find.byType(ListTile).first); + await tester.pumpAndSettle(); + + verify(() => mockGoRouter.go('/family/f1/person/p1')).called(1); + verifyNever(() => mockGoRouter.go('/family/f1/person/p2')); + }); + + testWidgets('should redirect to the PersonScreen if logged in', + (tester) async { + loginInfo.login('Username'); + + await tester.pumpWidget(ChangeNotifierProvider.value( + value: loginInfo, + child: Builder(builder: (context) { + final router = routerBuilder(context, '/family/f1/person/p1'); + + return MaterialApp.router( + routeInformationParser: router.routeInformationParser, + routerDelegate: router.routerDelegate, + debugShowCheckedModeBanner: false, + ); + }), + )); + + expect(find.byType(PersonScreen), findsOneWidget); + }); +}