Skip to content
This repository was archived by the owner on Feb 25, 2022. It is now read-only.

Add tests to Redirection Example #335

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go_router/example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
.pub-cache/
.pub/
build/
coverage/

# Web related
lib/generated_plugin_registrant.dart
Expand Down
114 changes: 60 additions & 54 deletions go_router/example/lib/redirection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,69 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) => ChangeNotifierProvider<LoginInfo>.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<LoginInfo>();
// 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<LoginInfo>(),
);

class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
Expand Down
1 change: 1 addition & 0 deletions go_router/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 31 additions & 0 deletions go_router/example/test/helpers/mock_go_router.dart
Original file line number Diff line number Diff line change
@@ -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,
);
}
110 changes: 110 additions & 0 deletions go_router/example/test/redirection_test.dart
Original file line number Diff line number Diff line change
@@ -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<LoginInfo>.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<LoginInfo>.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);
});
}