blob: e1b4000f4654c6a51740b440c4efe9fe5691efb3 [file] [log] [blame]
// 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.
// ignore_for_file: cascade_invocations, diagnostic_describe_all_properties
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:go_router/src/delegate.dart';
import 'package:go_router/src/match.dart';
import 'package:logging/logging.dart';
import 'test_helpers.dart';
const bool enableLogs = true;
final Logger log = Logger('GoRouter tests');
void main() {
if (enableLogs) {
Logger.root.onRecord.listen((LogRecord e) => debugPrint('$e'));
}
group('path routes', () {
testWidgets('match home route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen()),
];
final GoRouter router = await createRouter(routes, tester);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
});
testWidgets('If there is more than one route to match, use the first match',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/', builder: dummy),
GoRoute(path: '/', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/');
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
test('empty path', () {
expect(() {
GoRoute(path: '');
}, throwsA(isAssertionError));
});
test('leading / on sub-route', () {
expect(() {
GoRoute(
path: '/',
builder: dummy,
routes: <GoRoute>[
GoRoute(
path: '/foo',
builder: dummy,
),
],
);
}, throwsA(isAssertionError));
});
test('trailing / on sub-route', () {
expect(() {
GoRoute(
path: '/',
builder: dummy,
routes: <GoRoute>[
GoRoute(
path: 'foo/',
builder: dummy,
),
],
);
}, throwsA(isAssertionError));
});
testWidgets('lack of leading / on top-level route',
(WidgetTester tester) async {
await expectLater(() async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: 'foo', builder: dummy),
];
await createRouter(routes, tester);
}, throwsA(isAssertionError));
});
testWidgets('match no routes', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/foo');
await tester.pumpAndSettle();
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
});
testWidgets('match 2nd top level route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen()),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen()),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/login');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.subloc, '/login');
expect(router.screenFor(matches.first).runtimeType, LoginScreen);
});
testWidgets('match 2nd top level route with subroutes',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'page1',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen())
],
),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen()),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/login');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.subloc, '/login');
expect(router.screenFor(matches.first).runtimeType, LoginScreen);
});
testWidgets('match top level route when location has trailing /',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/login/');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.subloc, '/login');
expect(router.screenFor(matches.first).runtimeType, LoginScreen);
});
testWidgets('match top level route when location has trailing / (2)',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/profile', redirect: (_) => '/profile/foo'),
GoRoute(path: '/profile/:kind', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/profile/');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.subloc, '/profile/foo');
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
testWidgets('match top level route when location has trailing / (3)',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/profile', redirect: (_) => '/profile/foo'),
GoRoute(path: '/profile/:kind', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/profile/?bar=baz');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.subloc, '/profile/foo');
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
testWidgets('match sub-route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/login');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches.length, 2);
expect(matches.first.subloc, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
expect(matches[1].subloc, '/login');
expect(router.screenFor(matches[1]).runtimeType, LoginScreen);
});
testWidgets('match sub-routes', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) =>
const PersonScreen('dummy', 'dummy'),
),
],
),
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
{
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
}
router.go('/login');
{
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches.length, 2);
expect(matches.first.subloc, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
expect(matches[1].subloc, '/login');
expect(router.screenFor(matches[1]).runtimeType, LoginScreen);
}
router.go('/family/f2');
{
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches.length, 2);
expect(matches.first.subloc, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
expect(matches[1].subloc, '/family/f2');
expect(router.screenFor(matches[1]).runtimeType, FamilyScreen);
}
router.go('/family/f2/person/p1');
{
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches.length, 3);
expect(matches.first.subloc, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
expect(matches[1].subloc, '/family/f2');
expect(router.screenFor(matches[1]).runtimeType, FamilyScreen);
expect(matches[2].subloc, '/family/f2/person/p1');
expect(router.screenFor(matches[2]).runtimeType, PersonScreen);
}
});
testWidgets('return first matching route if too many subroutes',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'foo/bar',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen(''),
),
GoRoute(
path: 'bar',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
GoRoute(
path: 'foo',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
routes: <GoRoute>[
GoRoute(
path: 'bar',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/bar');
List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(2));
expect(router.screenFor(matches[1]).runtimeType, Page1Screen);
router.go('/foo/bar');
matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(2));
expect(router.screenFor(matches[1]).runtimeType, FamilyScreen);
router.go('/foo');
matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(2));
expect(router.screenFor(matches[1]).runtimeType, Page2Screen);
});
testWidgets('router state', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) {
expect(state.location, '/');
expect(state.subloc, '/');
expect(state.name, 'home');
expect(state.path, '/');
expect(state.fullpath, '/');
expect(state.params, <String, String>{});
expect(state.error, null);
if (state.extra != null) {
expect(state.extra! as int, 1);
}
return const HomeScreen();
},
routes: <GoRoute>[
GoRoute(
name: 'login',
path: 'login',
builder: (BuildContext context, GoRouterState state) {
expect(state.location, '/login');
expect(state.subloc, '/login');
expect(state.name, 'login');
expect(state.path, 'login');
expect(state.fullpath, '/login');
expect(state.params, <String, String>{});
expect(state.error, null);
expect(state.extra! as int, 2);
return const LoginScreen();
},
),
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) {
expect(
state.location,
anyOf(<String>['/family/f2', '/family/f2/person/p1']),
);
expect(state.subloc, '/family/f2');
expect(state.name, 'family');
expect(state.path, 'family/:fid');
expect(state.fullpath, '/family/:fid');
expect(state.params, <String, String>{'fid': 'f2'});
expect(state.error, null);
expect(state.extra! as int, 3);
return FamilyScreen(state.params['fid']!);
},
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
expect(state.location, '/family/f2/person/p1');
expect(state.subloc, '/family/f2/person/p1');
expect(state.name, 'person');
expect(state.path, 'person/:pid');
expect(state.fullpath, '/family/:fid/person/:pid');
expect(
state.params,
<String, String>{'fid': 'f2', 'pid': 'p1'},
);
expect(state.error, null);
expect(state.extra! as int, 4);
return PersonScreen(
state.params['fid']!, state.params['pid']!);
},
),
],
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/', extra: 1);
await tester.pump();
router.push('/login', extra: 2);
await tester.pump();
router.push('/family/f2', extra: 3);
await tester.pump();
router.push('/family/f2/person/p1', extra: 4);
await tester.pump();
});
testWidgets('match path case insensitively', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(state.params['fid']!),
),
];
final GoRouter router = await createRouter(routes, tester);
const String loc = '/FaMiLy/f2';
router.go(loc);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
// NOTE: match the lower case, since subloc is canonicalized to match the
// path case whereas the location can be any case; so long as the path
// produces a match regardless of the location case, we win!
expect(router.location.toLowerCase(), loc.toLowerCase());
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, FamilyScreen);
});
testWidgets(
'If there is more than one route to match, use the first match.',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(path: '/', builder: dummy),
GoRoute(path: '/page1', builder: dummy),
GoRoute(path: '/page1', builder: dummy),
GoRoute(path: '/:ok', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/user');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
});
});
group('named routes', () {
testWidgets('match home route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen()),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('home');
});
testWidgets('match too many routes', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(name: 'home', path: '/', builder: dummy),
GoRoute(name: 'home', path: '/', builder: dummy),
];
await expectLater(() async {
await createRouter(routes, tester);
}, throwsA(isAssertionError));
});
test('empty name', () {
expect(() {
GoRoute(name: '', path: '/');
}, throwsA(isAssertionError));
});
testWidgets('match no routes', (WidgetTester tester) async {
await expectLater(() async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(name: 'home', path: '/', builder: dummy),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('work');
}, throwsA(isAssertionError));
});
testWidgets('match 2nd top level route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
name: 'login',
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('login');
});
testWidgets('match sub-route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'login',
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('login');
});
testWidgets('match w/ params', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
expect(state.params,
<String, String>{'fid': 'f2', 'pid': 'p1'});
return const PersonScreen('dummy', 'dummy');
},
),
],
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('person',
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
});
testWidgets('too few params', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) =>
const PersonScreen('dummy', 'dummy'),
),
],
),
],
),
];
await expectLater(() async {
final GoRouter router = await createRouter(routes, tester);
router.goNamed('person', params: <String, String>{'fid': 'f2'});
await tester.pump();
}, throwsA(isAssertionError));
});
testWidgets('match case insensitive w/ params',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
name: 'PeRsOn',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
expect(state.params,
<String, String>{'fid': 'f2', 'pid': 'p1'});
return const PersonScreen('dummy', 'dummy');
},
),
],
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('person',
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
});
testWidgets('too few params', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'family',
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
),
];
await expectLater(() async {
final GoRouter router = await createRouter(routes, tester);
router.goNamed('family');
}, throwsA(isAssertionError));
});
testWidgets('too many params', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'family',
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
),
];
await expectLater(() async {
final GoRouter router = await createRouter(routes, tester);
router.goNamed('family',
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
}, throwsA(isAssertionError));
});
testWidgets('sparsely named routes', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
redirect: (_) => '/family/f2',
),
GoRoute(
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) => FamilyScreen(
state.params['fid']!,
),
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person:pid',
builder: (BuildContext context, GoRouterState state) =>
PersonScreen(
state.params['fid']!,
state.params['pid']!,
),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.goNamed('person',
params: <String, String>{'fid': 'f2', 'pid': 'p1'});
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.screenFor(matches.last).runtimeType, PersonScreen);
});
testWidgets('preserve path param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'page1',
path: '/page1/:param1',
builder: (BuildContext c, GoRouterState s) {
expect(s.params['param1'], param1);
return const DummyScreen();
},
),
];
final GoRouter router = await createRouter(routes, tester);
final String loc = router
.namedLocation('page1', params: <String, String>{'param1': param1});
log.info('loc= $loc');
router.go(loc);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
log.info('param1= ${matches.first.decodedParams['param1']}');
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
expect(matches.first.decodedParams['param1'], param1);
});
testWidgets('preserve query param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'page1',
path: '/page1',
builder: (BuildContext c, GoRouterState s) {
expect(s.queryParams['param1'], param1);
return const DummyScreen();
},
),
];
final GoRouter router = await createRouter(routes, tester);
final String loc = router.namedLocation('page1',
queryParams: <String, String>{'param1': param1});
router.go(loc);
await tester.pump();
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
expect(matches.first.queryParams['param1'], param1);
});
});
group('redirects', () {
testWidgets('top-level redirect', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen()),
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen()),
],
),
];
final GoRouter router = await createRouter(routes, tester,
redirect: (GoRouterState state) =>
state.subloc == '/login' ? null : '/login');
expect(router.location, '/login');
});
testWidgets('top-level redirect w/ named routes',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'dummy',
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
GoRoute(
name: 'login',
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(
routes,
tester,
redirect: (GoRouterState state) =>
state.subloc == '/login' ? null : state.namedLocation('login'),
);
expect(router.location, '/login');
});
testWidgets('route-level redirect', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
redirect: (GoRouterState state) => '/login',
),
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/dummy');
await tester.pump();
expect(router.location, '/login');
});
testWidgets('route-level redirect w/ named routes',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'dummy',
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
redirect: (GoRouterState state) => state.namedLocation('login'),
),
GoRoute(
name: 'login',
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/dummy');
await tester.pump();
expect(router.location, '/login');
});
testWidgets('multiple mixed redirect', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'dummy1',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
GoRoute(
path: 'dummy2',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
redirect: (GoRouterState state) => '/',
),
],
),
];
final GoRouter router = await createRouter(routes, tester,
redirect: (GoRouterState state) =>
state.subloc == '/dummy1' ? '/dummy2' : null);
router.go('/dummy1');
await tester.pump();
expect(router.location, '/');
});
testWidgets('top-level redirect loop', (WidgetTester tester) async {
final GoRouter router = await createRouter(<GoRoute>[], tester,
redirect: (GoRouterState state) => state.subloc == '/'
? '/login'
: state.subloc == '/login'
? '/'
: null);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
expect(
(router.screenFor(matches.first) as TestErrorScreen).ex, isNotNull);
log.info((router.screenFor(matches.first) as TestErrorScreen).ex);
});
testWidgets('route-level redirect loop', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(
path: '/',
redirect: (GoRouterState state) => '/login',
),
GoRoute(
path: '/login',
redirect: (GoRouterState state) => '/',
),
],
tester,
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
expect(
(router.screenFor(matches.first) as TestErrorScreen).ex, isNotNull);
log.info((router.screenFor(matches.first) as TestErrorScreen).ex);
});
testWidgets('mixed redirect loop', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(
path: '/login',
redirect: (GoRouterState state) => '/',
),
],
tester,
redirect: (GoRouterState state) =>
state.subloc == '/' ? '/login' : null,
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
expect(
(router.screenFor(matches.first) as TestErrorScreen).ex, isNotNull);
log.info((router.screenFor(matches.first) as TestErrorScreen).ex);
});
testWidgets('top-level redirect loop w/ query params',
(WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[],
tester,
redirect: (GoRouterState state) => state.subloc == '/'
? '/login?from=${state.location}'
: state.subloc == '/login'
? '/'
: null,
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
expect(
(router.screenFor(matches.first) as TestErrorScreen).ex, isNotNull);
log.info((router.screenFor(matches.first) as TestErrorScreen).ex);
});
testWidgets('expect null path/fullpath on top-level redirect',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/dummy',
redirect: (GoRouterState state) => '/',
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/dummy',
);
expect(router.location, '/');
});
testWidgets('top-level redirect state', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/login?from=/',
redirect: (GoRouterState state) {
expect(Uri.parse(state.location).queryParameters, isNotEmpty);
expect(Uri.parse(state.subloc).queryParameters, isEmpty);
expect(state.path, isNull);
expect(state.fullpath, isNull);
expect(state.params.length, 0);
expect(state.queryParams.length, 1);
expect(state.queryParams['from'], '/');
return null;
},
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, LoginScreen);
});
testWidgets('route-level redirect state', (WidgetTester tester) async {
const String loc = '/book/0';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/book/:bookId',
redirect: (GoRouterState state) {
expect(state.location, loc);
expect(state.subloc, loc);
expect(state.path, '/book/:bookId');
expect(state.fullpath, '/book/:bookId');
expect(state.params, <String, String>{'bookId': '0'});
expect(state.queryParams.length, 0);
return null;
},
builder: (BuildContext c, GoRouterState s) => const HomeScreen(),
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: loc,
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
});
testWidgets('sub-sub-route-level redirect params',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext c, GoRouterState s) => const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext c, GoRouterState s) =>
FamilyScreen(s.params['fid']!),
routes: <GoRoute>[
GoRoute(
path: 'person/:pid',
redirect: (GoRouterState s) {
expect(s.params['fid'], 'f2');
expect(s.params['pid'], 'p1');
return null;
},
builder: (BuildContext c, GoRouterState s) => PersonScreen(
s.params['fid']!,
s.params['pid']!,
),
),
],
),
],
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/family/f2/person/p1',
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches.length, 3);
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
expect(router.screenFor(matches[1]).runtimeType, FamilyScreen);
final PersonScreen page = router.screenFor(matches[2]) as PersonScreen;
expect(page.fid, 'f2');
expect(page.pid, 'p1');
});
testWidgets('redirect limit', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[],
tester,
redirect: (GoRouterState state) => '/${state.location}+',
redirectLimit: 10,
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, TestErrorScreen);
expect(
(router.screenFor(matches.first) as TestErrorScreen).ex, isNotNull);
log.info((router.screenFor(matches.first) as TestErrorScreen).ex);
});
testWidgets('extra not null in redirect', (WidgetTester tester) async {
bool isCallTopRedirect = false;
bool isCallRouteRedirect = false;
final List<GoRoute> routes = <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'login',
path: 'login',
builder: (BuildContext context, GoRouterState state) {
return const LoginScreen();
},
redirect: (GoRouterState state) {
isCallRouteRedirect = true;
expect(state.extra, isNotNull);
return null;
},
routes: <GoRoute>[],
),
],
),
];
final GoRouter router = await createRouter(
routes,
tester,
redirect: (GoRouterState state) {
if (state.location == '/login') {
isCallTopRedirect = true;
expect(state.extra, isNotNull);
}
return null;
},
);
router.go('/login', extra: 1);
await tester.pump();
expect(isCallTopRedirect, true);
expect(isCallRouteRedirect, true);
});
});
group('initial location', () {
testWidgets('initial location', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
],
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/dummy',
);
expect(router.location, '/dummy');
});
testWidgets('initial location w/ redirection', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/dummy',
redirect: (GoRouterState state) => '/',
),
];
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/dummy',
);
expect(router.location, '/');
});
testWidgets(
'does not take precedence over platformDispatcher.defaultRouteName',
(WidgetTester tester) async {
TestWidgetsFlutterBinding
.instance.platformDispatcher.defaultRouteNameTestValue = '/dummy';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
],
),
];
final GoRouter router = await createRouter(
routes,
tester,
);
expect(router.routeInformationProvider.value.location, '/dummy');
TestWidgetsFlutterBinding
.instance.platformDispatcher.defaultRouteNameTestValue = '/';
});
});
group('params', () {
testWidgets('preserve path param case', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(state.params['fid']!),
),
];
final GoRouter router = await createRouter(routes, tester);
for (final String fid in <String>['f2', 'F2']) {
final String loc = '/family/$fid';
router.go(loc);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.location, loc);
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, FamilyScreen);
expect(matches.first.decodedParams['fid'], fid);
}
});
testWidgets('preserve query param case', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/family',
builder: (BuildContext context, GoRouterState state) => FamilyScreen(
state.queryParams['fid']!,
),
),
];
final GoRouter router = await createRouter(routes, tester);
for (final String fid in <String>['f2', 'F2']) {
final String loc = '/family?fid=$fid';
router.go(loc);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.location, loc);
expect(matches, hasLength(1));
expect(router.screenFor(matches.first).runtimeType, FamilyScreen);
expect(matches.first.queryParams['fid'], fid);
}
});
testWidgets('preserve path param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/page1/:param1',
builder: (BuildContext c, GoRouterState s) {
expect(s.params['param1'], param1);
return const DummyScreen();
},
),
];
final GoRouter router = await createRouter(routes, tester);
final String loc = '/page1/${Uri.encodeComponent(param1)}';
router.go(loc);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
log.info('param1= ${matches.first.decodedParams['param1']}');
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
expect(matches.first.decodedParams['param1'], param1);
});
testWidgets('preserve query param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/page1',
builder: (BuildContext c, GoRouterState s) {
expect(s.queryParams['param1'], param1);
return const DummyScreen();
},
),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/page1?param1=$param1');
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.screenFor(matches.first).runtimeType, DummyScreen);
expect(matches.first.queryParams['param1'], param1);
final String loc = '/page1?param1=${Uri.encodeQueryComponent(param1)}';
router.go(loc);
final List<RouteMatch> matches2 = router.routerDelegate.matches.matches;
expect(router.screenFor(matches2[0]).runtimeType, DummyScreen);
expect(matches2[0].queryParams['param1'], param1);
});
test('error: duplicate path param', () {
try {
GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/:id/:blah/:bam/:id/:blah',
builder: dummy,
),
],
errorBuilder: (BuildContext context, GoRouterState state) =>
TestErrorScreen(state.error!),
initialLocation: '/0/1/2/0/1',
);
expect(false, true);
} on Exception catch (ex) {
log.info(ex);
}
});
testWidgets('duplicate query param', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
log.info('id= ${state.params['id']}');
expect(state.params.length, 0);
expect(state.queryParams.length, 1);
expect(state.queryParams['id'], anyOf('0', '1'));
return const HomeScreen();
},
),
],
tester,
initialLocation: '/?id=0&id=1',
);
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
});
testWidgets('duplicate path + query param', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(
path: '/:id',
builder: (BuildContext context, GoRouterState state) {
expect(state.params, <String, String>{'id': '0'});
expect(state.queryParams, <String, String>{'id': '1'});
return const HomeScreen();
},
),
],
tester,
);
router.go('/0?id=1');
await tester.pump();
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(matches, hasLength(1));
expect(matches.first.fullpath, '/:id');
expect(router.screenFor(matches.first).runtimeType, HomeScreen);
});
testWidgets('push + query param', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(path: '/', builder: dummy),
GoRoute(
path: '/family',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(
state.queryParams['fid']!,
),
),
GoRoute(
path: '/person',
builder: (BuildContext context, GoRouterState state) =>
PersonScreen(
state.queryParams['fid']!,
state.queryParams['pid']!,
),
),
],
tester,
);
router.go('/family?fid=f2');
await tester.pump();
router.push('/person?fid=f2&pid=p1');
await tester.pump();
final FamilyScreen page1 =
router.screenFor(router.routerDelegate.matches.matches.first)
as FamilyScreen;
expect(page1.fid, 'f2');
final PersonScreen page2 = router
.screenFor(router.routerDelegate.matches.matches[1]) as PersonScreen;
expect(page2.fid, 'f2');
expect(page2.pid, 'p1');
});
testWidgets('push + extra param', (WidgetTester tester) async {
final GoRouter router = await createRouter(
<GoRoute>[
GoRoute(path: '/', builder: dummy),
GoRoute(
path: '/family',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(
(state.extra! as Map<String, String>)['fid']!,
),
),
GoRoute(
path: '/person',
builder: (BuildContext context, GoRouterState state) =>
PersonScreen(
(state.extra! as Map<String, String>)['fid']!,
(state.extra! as Map<String, String>)['pid']!,
),
),
],
tester,
);
router.go('/family', extra: <String, String>{'fid': 'f2'});
await tester.pump();
router.push('/person', extra: <String, String>{'fid': 'f2', 'pid': 'p1'});
await tester.pump();
final FamilyScreen page1 =
router.screenFor(router.routerDelegate.matches.matches.first)
as FamilyScreen;
expect(page1.fid, 'f2');
final PersonScreen page2 = router
.screenFor(router.routerDelegate.matches.matches[1]) as PersonScreen;
expect(page2.fid, 'f2');
expect(page2.pid, 'p1');
});
testWidgets('keep param in nested route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(state.params['fid']!),
routes: <GoRoute>[
GoRoute(
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
final String fid = state.params['fid']!;
final String pid = state.params['pid']!;
return PersonScreen(fid, pid);
},
),
],
),
];
final GoRouter router = await createRouter(routes, tester);
const String fid = 'f1';
const String pid = 'p2';
const String loc = '/family/$fid/person/$pid';
router.push(loc);
await tester.pump();
final List<RouteMatch> matches = router.routerDelegate.matches.matches;
expect(router.location, loc);
expect(matches, hasLength(2));
expect(router.screenFor(matches.last).runtimeType, PersonScreen);
expect(matches.last.decodedParams['fid'], fid);
expect(matches.last.decodedParams['pid'], pid);
});
});
group('refresh listenable', () {
late StreamController<int> streamController;
setUpAll(() async {
streamController = StreamController<int>.broadcast();
await streamController.addStream(Stream<int>.value(0));
});
tearDownAll(() {
streamController.close();
});
group('stream', () {
test('no stream emits', () async {
// Act
final GoRouterRefreshStreamSpy notifyListener =
GoRouterRefreshStreamSpy(
streamController.stream,
);
// Assert
expect(notifyListener.notifyCount, equals(1));
// Cleanup
notifyListener.dispose();
});
test('three stream emits', () async {
// Arrange
final List<int> toEmit = <int>[1, 2, 3];
// Act
final GoRouterRefreshStreamSpy notifyListener =
GoRouterRefreshStreamSpy(
streamController.stream,
);
await streamController.addStream(Stream<int>.fromIterable(toEmit));
// Assert
expect(notifyListener.notifyCount, equals(toEmit.length + 1));
// Cleanup
notifyListener.dispose();
});
});
});
group('GoRouterHelper extensions', () {
final GlobalKey<DummyStatefulWidgetState> key =
GlobalKey<DummyStatefulWidgetState>();
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
name: 'home',
builder: (BuildContext context, GoRouterState state) =>
DummyStatefulWidget(key: key),
),
GoRoute(
path: '/page1',
name: 'page1',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
];
const String name = 'page1';
final Map<String, String> params = <String, String>{
'a-param-key': 'a-param-value',
};
final Map<String, String> queryParams = <String, String>{
'a-query-key': 'a-query-value',
};
const String location = '/page1';
const String extra = 'Hello';
testWidgets('calls [namedLocation] on closest GoRouter',
(WidgetTester tester) async {
final GoRouterNamedLocationSpy router =
GoRouterNamedLocationSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.namedLocation(
name,
params: params,
queryParams: queryParams,
);
expect(router.name, name);
expect(router.params, params);
expect(router.queryParams, queryParams);
});
testWidgets('calls [go] on closest GoRouter', (WidgetTester tester) async {
final GoRouterGoSpy router = GoRouterGoSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.go(
location,
extra: extra,
);
expect(router.myLocation, location);
expect(router.extra, extra);
});
testWidgets('calls [goNamed] on closest GoRouter',
(WidgetTester tester) async {
final GoRouterGoNamedSpy router = GoRouterGoNamedSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.goNamed(
name,
params: params,
queryParams: queryParams,
extra: extra,
);
expect(router.name, name);
expect(router.params, params);
expect(router.queryParams, queryParams);
expect(router.extra, extra);
});
testWidgets('calls [push] on closest GoRouter',
(WidgetTester tester) async {
final GoRouterPushSpy router = GoRouterPushSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.push(
location,
extra: extra,
);
expect(router.myLocation, location);
expect(router.extra, extra);
});
testWidgets('calls [pushNamed] on closest GoRouter',
(WidgetTester tester) async {
final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.pushNamed(
name,
params: params,
queryParams: queryParams,
extra: extra,
);
expect(router.name, name);
expect(router.params, params);
expect(router.queryParams, queryParams);
expect(router.extra, extra);
});
testWidgets('calls [pop] on closest GoRouter', (WidgetTester tester) async {
final GoRouterPopSpy router = GoRouterPopSpy(routes: routes);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
title: 'GoRouter Example',
),
);
key.currentContext!.pop();
expect(router.popped, true);
});
});
testWidgets('pop triggers pop on routerDelegate',
(WidgetTester tester) async {
final GoRouter router = await createGoRouter(tester)
..push('/error');
router.routerDelegate.addListener(expectAsync0(() {}));
router.pop();
await tester.pump();
});
testWidgets('didPush notifies listeners', (WidgetTester tester) async {
await createGoRouter(tester)
..addListener(expectAsync0(() {}))
..didPush(
MaterialPageRoute<void>(builder: (_) => const Text('Current route')),
MaterialPageRoute<void>(builder: (_) => const Text('Previous route')),
);
});
testWidgets('didPop notifies listeners', (WidgetTester tester) async {
await createGoRouter(tester)
..addListener(expectAsync0(() {}))
..didPop(
MaterialPageRoute<void>(builder: (_) => const Text('Current route')),
MaterialPageRoute<void>(builder: (_) => const Text('Previous route')),
);
});
testWidgets('didRemove notifies listeners', (WidgetTester tester) async {
await createGoRouter(tester)
..addListener(expectAsync0(() {}))
..didRemove(
MaterialPageRoute<void>(builder: (_) => const Text('Current route')),
MaterialPageRoute<void>(builder: (_) => const Text('Previous route')),
);
});
testWidgets('didReplace notifies listeners', (WidgetTester tester) async {
await createGoRouter(tester)
..addListener(expectAsync0(() {}))
..didReplace(
newRoute: MaterialPageRoute<void>(
builder: (_) => const Text('Current route'),
),
oldRoute: MaterialPageRoute<void>(
builder: (_) => const Text('Previous route'),
),
);
});
testWidgets('uses navigatorBuilder when provided',
(WidgetTester tester) async {
final Func3<Widget, BuildContext, GoRouterState, Widget> navigatorBuilder =
expectAsync3(fakeNavigationBuilder);
final GoRouter router = GoRouter(
initialLocation: '/',
routes: <GoRoute>[
GoRoute(path: '/', builder: (_, __) => const DummyStatefulWidget()),
GoRoute(
path: '/error',
builder: (_, __) => TestErrorScreen(TestFailure('exception')),
),
],
navigatorBuilder: navigatorBuilder,
);
final GoRouterDelegate delegate = router.routerDelegate;
delegate.builder.builderWithNav(
DummyBuildContext(),
GoRouterState(router.routeConfiguration,
location: '/foo', subloc: '/bar', name: 'baz'),
const Navigator(),
);
});
}