Skip to content

Commit a9d6bf1

Browse files
authored
[go_router] improve coverage (#977)
1 parent 24774ac commit a9d6bf1

13 files changed

+1024
-19
lines changed

packages/go_router/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
## NEXT
1+
## 3.0.5
22

33
- Add `dispatchNotification` method to `DummyBuildContext` in tests. (This
44
should be revisited when Flutter `2.11.0` becomes stable.)
5+
- Improves code coverage.
6+
- `GoRoute` now warns about requiring either `pageBuilder`, `builder` or `redirect` at instantiation.
57

68
## 3.0.4
79

packages/go_router/lib/src/go_route.dart

+17-8
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class GoRoute {
1818
required this.path,
1919
this.name,
2020
this.pageBuilder,
21-
this.builder = _builder,
21+
this.builder = _invalidBuilder,
2222
this.routes = const <GoRoute>[],
23-
this.redirect = _redirect,
23+
this.redirect = _noRedirection,
2424
}) {
2525
if (path.isEmpty) {
2626
throw Exception('GoRoute path cannot be empty');
@@ -30,6 +30,15 @@ class GoRoute {
3030
throw Exception('GoRoute name cannot be empty');
3131
}
3232

33+
if (pageBuilder == null &&
34+
builder == _invalidBuilder &&
35+
redirect == _noRedirection) {
36+
throw Exception(
37+
'GoRoute builder parameter not set\n'
38+
'See gorouter.dev/redirection#considerations for details',
39+
);
40+
}
41+
3342
// cache the path regexp and parameters
3443
_pathRE = patternToRegExp(path, _pathParams);
3544

@@ -199,11 +208,11 @@ class GoRoute {
199208
Map<String, String> extractPathParams(RegExpMatch match) =>
200209
extractPathParameters(_pathParams, match);
201210

202-
static String? _redirect(GoRouterState state) => null;
211+
static String? _noRedirection(GoRouterState state) => null;
203212

204-
static Widget _builder(BuildContext context, GoRouterState state) =>
205-
throw Exception(
206-
'GoRoute builder parameter not set\n'
207-
'See gorouter.dev/redirection#considerations for details',
208-
);
213+
static Widget _invalidBuilder(
214+
BuildContext context,
215+
GoRouterState state,
216+
) =>
217+
const SizedBox.shrink();
209218
}

packages/go_router/lib/src/inherited_go_router.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class InheritedGoRouter extends InheritedWidget {
2525
/// Used by the Router architecture as part of the InheritedWidget.
2626
@override
2727
// ignore: prefer_expression_function_bodies
28-
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
28+
bool updateShouldNotify(covariant InheritedGoRouter oldWidget) {
2929
// avoid rebuilding the widget tree if the router has not changed
30-
return false;
30+
return goRouter != oldWidget.goRouter;
3131
}
3232

3333
@override

packages/go_router/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: go_router
22
description: A declarative router for Flutter based on Navigation 2 supporting
33
deep linking, data-driven routes and more
4-
version: 3.0.4
4+
version: 3.0.5
55
repository: https://github.com/flutter/packages/tree/main/packages/go_router
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
77

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:go_router/go_router.dart';
8+
9+
void main() {
10+
testWidgets('CustomTransitionPage builds its child using transitionsBuilder',
11+
(WidgetTester tester) async {
12+
const HomeScreen child = HomeScreen();
13+
final CustomTransitionPage<void> transition = CustomTransitionPage<void>(
14+
transitionsBuilder: expectAsync4((_, __, ___, Widget child) => child),
15+
child: child,
16+
);
17+
final GoRouter router = GoRouter(
18+
routes: <GoRoute>[
19+
GoRoute(
20+
path: '/',
21+
pageBuilder: (_, __) => transition,
22+
),
23+
],
24+
);
25+
await tester.pumpWidget(
26+
MaterialApp.router(
27+
routeInformationParser: router.routeInformationParser,
28+
routerDelegate: router.routerDelegate,
29+
title: 'GoRouter Example',
30+
),
31+
);
32+
expect(find.byWidget(child), findsOneWidget);
33+
});
34+
35+
testWidgets('NoTransitionPage does not apply any transition',
36+
(WidgetTester tester) async {
37+
final ValueNotifier<bool> showHomeValueNotifier =
38+
ValueNotifier<bool>(false);
39+
await tester.pumpWidget(
40+
MaterialApp(
41+
home: ValueListenableBuilder<bool>(
42+
valueListenable: showHomeValueNotifier,
43+
builder: (_, bool showHome, __) {
44+
return Navigator(
45+
pages: <Page<void>>[
46+
const NoTransitionPage<void>(
47+
child: LoginScreen(),
48+
),
49+
if (showHome)
50+
const NoTransitionPage<void>(
51+
child: HomeScreen(),
52+
),
53+
],
54+
onPopPage: (Route<dynamic> route, dynamic result) {
55+
return route.didPop(result);
56+
},
57+
);
58+
},
59+
),
60+
),
61+
);
62+
63+
final Finder homeScreenFinder = find.byType(HomeScreen);
64+
65+
showHomeValueNotifier.value = true;
66+
await tester.pump();
67+
final Offset homeScreenPositionInTheMiddleOfAddition =
68+
tester.getTopLeft(homeScreenFinder);
69+
await tester.pumpAndSettle();
70+
final Offset homeScreenPositionAfterAddition =
71+
tester.getTopLeft(homeScreenFinder);
72+
73+
showHomeValueNotifier.value = false;
74+
await tester.pump();
75+
final Offset homeScreenPositionInTheMiddleOfRemoval =
76+
tester.getTopLeft(homeScreenFinder);
77+
await tester.pumpAndSettle();
78+
79+
expect(
80+
homeScreenPositionInTheMiddleOfAddition,
81+
homeScreenPositionAfterAddition,
82+
);
83+
expect(
84+
homeScreenPositionAfterAddition,
85+
homeScreenPositionInTheMiddleOfRemoval,
86+
);
87+
});
88+
}
89+
90+
class HomeScreen extends StatelessWidget {
91+
const HomeScreen({Key? key}) : super(key: key);
92+
93+
@override
94+
Widget build(BuildContext context) {
95+
return const Scaffold(
96+
body: Center(
97+
child: Text('HomeScreen'),
98+
),
99+
);
100+
}
101+
}
102+
103+
class LoginScreen extends StatelessWidget {
104+
const LoginScreen({Key? key}) : super(key: key);
105+
106+
@override
107+
Widget build(BuildContext context) {
108+
return const Scaffold(
109+
body: Center(
110+
child: Text('LoginScreen'),
111+
),
112+
);
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/cupertino.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:go_router/go_router.dart';
9+
10+
import 'go_router_test.dart';
11+
12+
WidgetTesterCallback testPageNotFound({required Widget widget}) {
13+
return (WidgetTester tester) async {
14+
await tester.pumpWidget(widget);
15+
expect(find.text('page not found'), findsOneWidget);
16+
};
17+
}
18+
19+
WidgetTesterCallback testPageShowsExceptionMessage({
20+
required Exception exception,
21+
required Widget widget,
22+
}) {
23+
return (WidgetTester tester) async {
24+
await tester.pumpWidget(widget);
25+
expect(find.text('$exception'), findsOneWidget);
26+
};
27+
}
28+
29+
WidgetTesterCallback testClickingTheButtonRedirectsToRoot({
30+
required Finder buttonFinder,
31+
required Widget widget,
32+
Widget Function(GoRouter router) appRouterBuilder = materialAppRouterBuilder,
33+
}) {
34+
return (WidgetTester tester) async {
35+
final GoRouter router = GoRouter(
36+
initialLocation: '/error',
37+
routes: <GoRoute>[
38+
GoRoute(path: '/', builder: (_, __) => const DummyStatefulWidget()),
39+
GoRoute(
40+
path: '/error',
41+
builder: (_, __) => widget,
42+
),
43+
],
44+
);
45+
await tester.pumpWidget(appRouterBuilder(router));
46+
await tester.tap(buttonFinder);
47+
await tester.pumpAndSettle();
48+
expect(find.byType(DummyStatefulWidget), findsOneWidget);
49+
};
50+
}
51+
52+
Widget materialAppRouterBuilder(GoRouter router) {
53+
return MaterialApp.router(
54+
routeInformationParser: router.routeInformationParser,
55+
routerDelegate: router.routerDelegate,
56+
title: 'GoRouter Example',
57+
);
58+
}
59+
60+
Widget cupertinoAppRouterBuilder(GoRouter router) {
61+
return CupertinoApp.router(
62+
routeInformationParser: router.routeInformationParser,
63+
routerDelegate: router.routerDelegate,
64+
title: 'GoRouter Example',
65+
);
66+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:go_router/go_router.dart';
7+
8+
void main() {
9+
test('throws when a builder is not set', () {
10+
expect(() => GoRoute(path: '/'), throwsException);
11+
});
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/cupertino.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:go_router/src/go_router_cupertino.dart';
9+
10+
import 'error_screen_helpers.dart';
11+
12+
void main() {
13+
group('isCupertinoApp', () {
14+
testWidgets('returns [true] when CupertinoApp is present',
15+
(WidgetTester tester) async {
16+
final GlobalKey<_DummyStatefulWidgetState> key =
17+
GlobalKey<_DummyStatefulWidgetState>();
18+
await tester.pumpWidget(
19+
CupertinoApp(
20+
home: DummyStatefulWidget(key: key),
21+
),
22+
);
23+
final bool isCupertino = isCupertinoApp(key.currentContext! as Element);
24+
expect(isCupertino, true);
25+
});
26+
27+
testWidgets('returns [false] when MaterialApp is present',
28+
(WidgetTester tester) async {
29+
final GlobalKey<_DummyStatefulWidgetState> key =
30+
GlobalKey<_DummyStatefulWidgetState>();
31+
await tester.pumpWidget(
32+
MaterialApp(
33+
home: DummyStatefulWidget(key: key),
34+
),
35+
);
36+
final bool isCupertino = isCupertinoApp(key.currentContext! as Element);
37+
expect(isCupertino, false);
38+
});
39+
});
40+
41+
test('pageBuilderForCupertinoApp creates a [CupertinoPage] accordingly', () {
42+
final UniqueKey key = UniqueKey();
43+
const String name = 'name';
44+
const String arguments = 'arguments';
45+
const String restorationId = 'restorationId';
46+
const DummyStatefulWidget child = DummyStatefulWidget();
47+
final CupertinoPage<void> page = pageBuilderForCupertinoApp(
48+
key: key,
49+
name: name,
50+
arguments: arguments,
51+
restorationId: restorationId,
52+
child: child,
53+
);
54+
expect(page.key, key);
55+
expect(page.name, name);
56+
expect(page.arguments, arguments);
57+
expect(page.restorationId, restorationId);
58+
expect(page.child, child);
59+
});
60+
61+
group('GoRouterCupertinoErrorScreen', () {
62+
testWidgets(
63+
'shows "page not found" by default',
64+
testPageNotFound(
65+
widget: const CupertinoApp(
66+
home: GoRouterCupertinoErrorScreen(null),
67+
),
68+
),
69+
);
70+
71+
final Exception exception = Exception('Something went wrong!');
72+
testWidgets(
73+
'shows the exception message when provided',
74+
testPageShowsExceptionMessage(
75+
exception: exception,
76+
widget: CupertinoApp(
77+
home: GoRouterCupertinoErrorScreen(exception),
78+
),
79+
),
80+
);
81+
82+
testWidgets(
83+
'clicking the CupertinoButton should redirect to /',
84+
testClickingTheButtonRedirectsToRoot(
85+
buttonFinder: find.byType(CupertinoButton),
86+
appRouterBuilder: cupertinoAppRouterBuilder,
87+
widget: const CupertinoApp(
88+
home: GoRouterCupertinoErrorScreen(null),
89+
),
90+
),
91+
);
92+
});
93+
}
94+
95+
class DummyStatefulWidget extends StatefulWidget {
96+
const DummyStatefulWidget({Key? key}) : super(key: key);
97+
98+
@override
99+
State<DummyStatefulWidget> createState() => _DummyStatefulWidgetState();
100+
}
101+
102+
class _DummyStatefulWidgetState extends State<DummyStatefulWidget> {
103+
@override
104+
Widget build(BuildContext context) => Container();
105+
}

0 commit comments

Comments
 (0)