blob: 69364f2aa8019f49f3ed49e7da9648fe34830128 [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.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:go_router/src/state.dart';
import 'test_helpers.dart';
void main() {
group('GoRouterState from context', () {
testWidgets('works in builder', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, _) {
final GoRouterState state = GoRouterState.of(context);
return Text('/ ${state.uri.queryParameters['p']}');
}),
GoRoute(
path: '/a',
builder: (BuildContext context, _) {
final GoRouterState state = GoRouterState.of(context);
return Text('/a ${state.uri.queryParameters['p']}');
}),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/?p=123');
await tester.pumpAndSettle();
expect(find.text('/ 123'), findsOneWidget);
router.go('/a?p=456');
await tester.pumpAndSettle();
expect(find.text('/a 456'), findsOneWidget);
});
testWidgets('works in subtree', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text('1 ${GoRouterState.of(context).uri.path}');
});
},
routes: <GoRoute>[
GoRoute(
path: 'a',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text('2 ${GoRouterState.of(context).uri.path}');
});
}),
]),
];
final GoRouter router = await createRouter(routes, tester);
router.go('/');
await tester.pumpAndSettle();
expect(find.text('1 /'), findsOneWidget);
router.go('/a');
await tester.pumpAndSettle();
expect(find.text('2 /a'), findsOneWidget);
// The query parameter is removed, so is the location in first page.
expect(find.text('1 /a', skipOffstage: false), findsOneWidget);
});
testWidgets('path parameter persists after page is popped',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text('1 ${GoRouterState.of(context).uri.path}');
});
},
routes: <GoRoute>[
GoRoute(
path: ':id',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text(
'2 ${GoRouterState.of(context).pathParameters['id']}');
});
}),
]),
];
final GoRouter router = await createRouter(routes, tester);
await tester.pumpAndSettle();
expect(find.text('1 /'), findsOneWidget);
router.go('/123');
await tester.pumpAndSettle();
expect(find.text('2 123'), findsOneWidget);
router.pop();
await tester.pump();
// Page 2 is in popping animation but should still be on screen with the
// correct path parameter.
expect(find.text('2 123'), findsOneWidget);
});
testWidgets('registry retains GoRouterState for exiting route',
(WidgetTester tester) async {
final UniqueKey key = UniqueKey();
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text(GoRouterState.of(context).uri.path);
});
},
routes: <GoRoute>[
GoRoute(
path: 'a',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text(key: key, GoRouterState.of(context).uri.path);
});
}),
]),
];
final GoRouter router =
await createRouter(routes, tester, initialLocation: '/a');
expect(tester.widget<Text>(find.byKey(key)).data, '/a');
final GoRouterStateRegistry registry = tester
.widget<GoRouterStateRegistryScope>(
find.byType(GoRouterStateRegistryScope))
.notifier!;
expect(registry.registry.length, 2);
router.go('/');
await tester.pump();
expect(registry.registry.length, 2);
// should retain the same location even if the location has changed.
expect(tester.widget<Text>(find.byKey(key)).data, '/a');
// Finish the pop animation.
await tester.pumpAndSettle();
expect(registry.registry.length, 1);
expect(find.byKey(key), findsNothing);
});
testWidgets('imperative pop clears out registry',
(WidgetTester tester) async {
final UniqueKey key = UniqueKey();
final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text(GoRouterState.of(context).uri.path);
});
},
routes: <GoRoute>[
GoRoute(
path: 'a',
builder: (_, __) {
return Builder(builder: (BuildContext context) {
return Text(key: key, GoRouterState.of(context).uri.path);
});
}),
]),
];
await createRouter(routes, tester,
initialLocation: '/a', navigatorKey: nav);
expect(tester.widget<Text>(find.byKey(key)).data, '/a');
final GoRouterStateRegistry registry = tester
.widget<GoRouterStateRegistryScope>(
find.byType(GoRouterStateRegistryScope))
.notifier!;
expect(registry.registry.length, 2);
nav.currentState!.pop();
await tester.pump();
expect(registry.registry.length, 2);
// should retain the same location even if the location has changed.
expect(tester.widget<Text>(find.byKey(key)).data, '/a');
// Finish the pop animation.
await tester.pumpAndSettle();
expect(registry.registry.length, 1);
expect(find.byKey(key), findsNothing);
});
testWidgets('GoRouterState topRoute accessible from StatefulShellRoute',
(WidgetTester tester) async {
final GlobalKey<NavigatorState> rootNavigatorKey =
GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> shellNavigatorKey =
GlobalKey<NavigatorState>();
final List<RouteBase> routes = <RouteBase>[
ShellRoute(
navigatorKey: shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return Scaffold(
body: Column(
children: <Widget>[
const Text('Screen 0'),
Expanded(child: child),
],
),
);
},
routes: <RouteBase>[
GoRoute(
name: 'root',
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Text('Screen 1'),
);
},
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
parentNavigatorKey: rootNavigatorKey,
builder: (
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
final String? routeName =
GoRouterState.of(context).topRoute?.name;
final String title = switch (routeName) {
'a' => 'A',
'b' => 'B',
_ => 'Unknown',
};
return Column(
children: <Widget>[
Text(title),
Expanded(child: navigationShell),
],
);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
name: 'a',
path: 'a',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Text('Screen 2'),
);
},
),
],
),
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
name: 'b',
path: 'b',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Text('Screen 2'),
);
},
),
],
)
],
),
],
)
],
),
];
final GoRouter router = await createRouter(routes, tester,
initialLocation: '/a', navigatorKey: rootNavigatorKey);
expect(find.text('A'), findsOneWidget);
router.go('/b');
await tester.pumpAndSettle();
expect(find.text('B'), findsOneWidget);
});
});
}