Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c185e4b
test: adds `GoRouterHelper` tests
Ascenio Feb 19, 2022
4c5d57b
test: adds `InheritedGoRouter` tests
Ascenio Feb 19, 2022
2b9b1ad
test: adds cupertino related tests
Ascenio Feb 19, 2022
27be0f0
fix: switches from backticks to square brackets
Ascenio Feb 19, 2022
fb08dc9
test: adds material related tests
Ascenio Feb 19, 2022
1c4a1e4
test: adds `GoRouterErrorScreen` tests
Ascenio Feb 19, 2022
5543ace
test: adds transitions related tests
Ascenio Feb 19, 2022
7a658f9
test: adds `GoRouterDelegate` tests
Ascenio Feb 19, 2022
03c7a08
test: adds `GoRouter` tests
Ascenio Feb 19, 2022
031a3bb
test: adds `GoRoute` tests
Ascenio Feb 19, 2022
a8811d4
test: adds more transitions related tests
Ascenio Feb 20, 2022
8de564b
docs: updates changelog
Ascenio Feb 20, 2022
4cf6986
refactor: moves the tests to correct folder
Ascenio Feb 28, 2022
f6dacbf
chore: adds license header to tests
Ascenio Mar 1, 2022
0775847
fix: asserts on expected values of GoRouterTest
Ascenio Mar 1, 2022
fbe702b
refactor: renames extensions' tests
Ascenio Mar 1, 2022
369a8b8
refactor: merges GoRouter related tests
Ascenio Mar 1, 2022
cffa96c
chore: applies latest lint changes
Ascenio Mar 6, 2022
f87f52e
refactor: tests `NoTransitionPage` based on widget's position
Ascenio Mar 7, 2022
06933d8
refactor: small formatting on widget test
Ascenio Mar 7, 2022
563205e
chore: bump version
Ascenio Mar 8, 2022
d191c94
refactor: move tests to `test/`
Ascenio Mar 8, 2022
f5128fc
fix: fixes import
Ascenio Mar 10, 2022
28594db
Merge branch 'main' into main
Ascenio Mar 18, 2022
7bca0ca
docs: updates CHANGELOG
Ascenio Mar 19, 2022
836a8c6
refactor: rename `MockGoRouterRefreshStream` to `GoRouterRefreshStrea…
Ascenio Mar 19, 2022
e06b294
refactor: rename test
Ascenio Mar 19, 2022
bf52462
refactor: extract error screens' tests into helpers
Ascenio Mar 19, 2022
8d105cc
docs: adds missing license to test helpers
Ascenio Mar 19, 2022
36373d3
feat: make test helpers integrate better with IDEs
Ascenio Mar 19, 2022
fce5f44
feat: makes InheritedGoRouter update when goRouter changes
Ascenio Mar 19, 2022
d51df66
feat: warn users about required builder on `GoRoute`
Ascenio Mar 19, 2022
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
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