Skip to content

Commit

Permalink
[go_router] improve coverage (#977)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ascenio authored Mar 21, 2022
1 parent 24774ac commit a9d6bf1
Show file tree
Hide file tree
Showing 13 changed files with 1,024 additions and 19 deletions.
4 changes: 3 additions & 1 deletion packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## NEXT
## 3.0.5

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

## 3.0.4

Expand Down
25 changes: 17 additions & 8 deletions packages/go_router/lib/src/go_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class GoRoute {
required this.path,
this.name,
this.pageBuilder,
this.builder = _builder,
this.builder = _invalidBuilder,
this.routes = const <GoRoute>[],
this.redirect = _redirect,
this.redirect = _noRedirection,
}) {
if (path.isEmpty) {
throw Exception('GoRoute path cannot be empty');
Expand All @@ -30,6 +30,15 @@ class GoRoute {
throw Exception('GoRoute name cannot be empty');
}

if (pageBuilder == null &&
builder == _invalidBuilder &&
redirect == _noRedirection) {
throw Exception(
'GoRoute builder parameter not set\n'
'See gorouter.dev/redirection#considerations for details',
);
}

// cache the path regexp and parameters
_pathRE = patternToRegExp(path, _pathParams);

Expand Down Expand Up @@ -199,11 +208,11 @@ class GoRoute {
Map<String, String> extractPathParams(RegExpMatch match) =>
extractPathParameters(_pathParams, match);

static String? _redirect(GoRouterState state) => null;
static String? _noRedirection(GoRouterState state) => null;

static Widget _builder(BuildContext context, GoRouterState state) =>
throw Exception(
'GoRoute builder parameter not set\n'
'See gorouter.dev/redirection#considerations for details',
);
static Widget _invalidBuilder(
BuildContext context,
GoRouterState state,
) =>
const SizedBox.shrink();
}
4 changes: 2 additions & 2 deletions packages/go_router/lib/src/inherited_go_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class InheritedGoRouter extends InheritedWidget {
/// Used by the Router architecture as part of the InheritedWidget.
@override
// ignore: prefer_expression_function_bodies
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
bool updateShouldNotify(covariant InheritedGoRouter oldWidget) {
// avoid rebuilding the widget tree if the router has not changed
return false;
return goRouter != oldWidget.goRouter;
}

@override
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
version: 3.0.4
version: 3.0.5
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22

Expand Down
114 changes: 114 additions & 0 deletions packages/go_router/test/custom_transition_page_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

void main() {
testWidgets('CustomTransitionPage builds its child using transitionsBuilder',
(WidgetTester tester) async {
const HomeScreen child = HomeScreen();
final CustomTransitionPage<void> transition = CustomTransitionPage<void>(
transitionsBuilder: expectAsync4((_, __, ___, Widget child) => child),
child: child,
);
final GoRouter router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
pageBuilder: (_, __) => transition,
),
],
);
await tester.pumpWidget(
MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
expect(find.byWidget(child), findsOneWidget);
});

testWidgets('NoTransitionPage does not apply any transition',
(WidgetTester tester) async {
final ValueNotifier<bool> showHomeValueNotifier =
ValueNotifier<bool>(false);
await tester.pumpWidget(
MaterialApp(
home: ValueListenableBuilder<bool>(
valueListenable: showHomeValueNotifier,
builder: (_, bool showHome, __) {
return Navigator(
pages: <Page<void>>[
const NoTransitionPage<void>(
child: LoginScreen(),
),
if (showHome)
const NoTransitionPage<void>(
child: HomeScreen(),
),
],
onPopPage: (Route<dynamic> route, dynamic result) {
return route.didPop(result);
},
);
},
),
),
);

final Finder homeScreenFinder = find.byType(HomeScreen);

showHomeValueNotifier.value = true;
await tester.pump();
final Offset homeScreenPositionInTheMiddleOfAddition =
tester.getTopLeft(homeScreenFinder);
await tester.pumpAndSettle();
final Offset homeScreenPositionAfterAddition =
tester.getTopLeft(homeScreenFinder);

showHomeValueNotifier.value = false;
await tester.pump();
final Offset homeScreenPositionInTheMiddleOfRemoval =
tester.getTopLeft(homeScreenFinder);
await tester.pumpAndSettle();

expect(
homeScreenPositionInTheMiddleOfAddition,
homeScreenPositionAfterAddition,
);
expect(
homeScreenPositionAfterAddition,
homeScreenPositionInTheMiddleOfRemoval,
);
});
}

class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('HomeScreen'),
),
);
}
}

class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('LoginScreen'),
),
);
}
}
66 changes: 66 additions & 0 deletions packages/go_router/test/error_screen_helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

import 'go_router_test.dart';

WidgetTesterCallback testPageNotFound({required Widget widget}) {
return (WidgetTester tester) async {
await tester.pumpWidget(widget);
expect(find.text('page not found'), findsOneWidget);
};
}

WidgetTesterCallback testPageShowsExceptionMessage({
required Exception exception,
required Widget widget,
}) {
return (WidgetTester tester) async {
await tester.pumpWidget(widget);
expect(find.text('$exception'), findsOneWidget);
};
}

WidgetTesterCallback testClickingTheButtonRedirectsToRoot({
required Finder buttonFinder,
required Widget widget,
Widget Function(GoRouter router) appRouterBuilder = materialAppRouterBuilder,
}) {
return (WidgetTester tester) async {
final GoRouter router = GoRouter(
initialLocation: '/error',
routes: <GoRoute>[
GoRoute(path: '/', builder: (_, __) => const DummyStatefulWidget()),
GoRoute(
path: '/error',
builder: (_, __) => widget,
),
],
);
await tester.pumpWidget(appRouterBuilder(router));
await tester.tap(buttonFinder);
await tester.pumpAndSettle();
expect(find.byType(DummyStatefulWidget), findsOneWidget);
};
}

Widget materialAppRouterBuilder(GoRouter router) {
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
);
}

Widget cupertinoAppRouterBuilder(GoRouter router) {
return CupertinoApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
);
}
12 changes: 12 additions & 0 deletions packages/go_router/test/go_route_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

void main() {
test('throws when a builder is not set', () {
expect(() => GoRoute(path: '/'), throwsException);
});
}
105 changes: 105 additions & 0 deletions packages/go_router/test/go_router_cupertino_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/src/go_router_cupertino.dart';

import 'error_screen_helpers.dart';

void main() {
group('isCupertinoApp', () {
testWidgets('returns [true] when CupertinoApp is present',
(WidgetTester tester) async {
final GlobalKey<_DummyStatefulWidgetState> key =
GlobalKey<_DummyStatefulWidgetState>();
await tester.pumpWidget(
CupertinoApp(
home: DummyStatefulWidget(key: key),
),
);
final bool isCupertino = isCupertinoApp(key.currentContext! as Element);
expect(isCupertino, true);
});

testWidgets('returns [false] when MaterialApp is present',
(WidgetTester tester) async {
final GlobalKey<_DummyStatefulWidgetState> key =
GlobalKey<_DummyStatefulWidgetState>();
await tester.pumpWidget(
MaterialApp(
home: DummyStatefulWidget(key: key),
),
);
final bool isCupertino = isCupertinoApp(key.currentContext! as Element);
expect(isCupertino, false);
});
});

test('pageBuilderForCupertinoApp creates a [CupertinoPage] accordingly', () {
final UniqueKey key = UniqueKey();
const String name = 'name';
const String arguments = 'arguments';
const String restorationId = 'restorationId';
const DummyStatefulWidget child = DummyStatefulWidget();
final CupertinoPage<void> page = pageBuilderForCupertinoApp(
key: key,
name: name,
arguments: arguments,
restorationId: restorationId,
child: child,
);
expect(page.key, key);
expect(page.name, name);
expect(page.arguments, arguments);
expect(page.restorationId, restorationId);
expect(page.child, child);
});

group('GoRouterCupertinoErrorScreen', () {
testWidgets(
'shows "page not found" by default',
testPageNotFound(
widget: const CupertinoApp(
home: GoRouterCupertinoErrorScreen(null),
),
),
);

final Exception exception = Exception('Something went wrong!');
testWidgets(
'shows the exception message when provided',
testPageShowsExceptionMessage(
exception: exception,
widget: CupertinoApp(
home: GoRouterCupertinoErrorScreen(exception),
),
),
);

testWidgets(
'clicking the CupertinoButton should redirect to /',
testClickingTheButtonRedirectsToRoot(
buttonFinder: find.byType(CupertinoButton),
appRouterBuilder: cupertinoAppRouterBuilder,
widget: const CupertinoApp(
home: GoRouterCupertinoErrorScreen(null),
),
),
);
});
}

class DummyStatefulWidget extends StatefulWidget {
const DummyStatefulWidget({Key? key}) : super(key: key);

@override
State<DummyStatefulWidget> createState() => _DummyStatefulWidgetState();
}

class _DummyStatefulWidgetState extends State<DummyStatefulWidget> {
@override
Widget build(BuildContext context) => Container();
}
Loading

0 comments on commit a9d6bf1

Please sign in to comment.