1// Copyright 2014 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@TestOn('!chrome')
6library;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/material.dart';
10import 'package:flutter/services.dart';
11import 'package:flutter_test/flutter_test.dart';
12
13class OnTapPage extends StatelessWidget {
14 const OnTapPage({super.key, required this.id, required this.onTap});
15
16 final String id;
17 final VoidCallback onTap;
18
19 @override
20 Widget build(BuildContext context) {
21 return Scaffold(
22 appBar: AppBar(title: Text('Page $id')),
23 body: GestureDetector(
24 onTap: onTap,
25 behavior: HitTestBehavior.opaque,
26 child: Center(child: Text(id, style: Theme.of(context).textTheme.displaySmall)),
27 ),
28 );
29 }
30}
31
32void main() {
33 testWidgets('Push and Pop should send platform messages', (WidgetTester tester) async {
34 final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
35 '/':
36 (BuildContext context) => OnTapPage(
37 id: '/',
38 onTap: () {
39 Navigator.pushNamed(context, '/A');
40 },
41 ),
42 '/A':
43 (BuildContext context) => OnTapPage(
44 id: 'A',
45 onTap: () {
46 Navigator.pop(context);
47 },
48 ),
49 };
50
51 final List<MethodCall> log = <MethodCall>[];
52
53 tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (
54 MethodCall methodCall,
55 ) async {
56 log.add(methodCall);
57 return null;
58 });
59
60 await tester.pumpWidget(MaterialApp(routes: routes));
61
62 expect(log, <Object>[
63 isMethodCall('selectSingleEntryHistory', arguments: null),
64 isMethodCall(
65 'routeInformationUpdated',
66 arguments: <String, dynamic>{'uri': '/', 'state': null, 'replace': false},
67 ),
68 ]);
69 log.clear();
70
71 await tester.tap(find.text('/'));
72 await tester.pump();
73 await tester.pump(const Duration(seconds: 1));
74
75 expect(log, hasLength(1));
76 expect(
77 log.last,
78 isMethodCall(
79 'routeInformationUpdated',
80 arguments: <String, dynamic>{'uri': '/A', 'state': null, 'replace': false},
81 ),
82 );
83 log.clear();
84
85 await tester.tap(find.text('A'));
86 await tester.pump();
87 await tester.pump(const Duration(seconds: 1));
88
89 expect(log, hasLength(1));
90 expect(
91 log.last,
92 isMethodCall(
93 'routeInformationUpdated',
94 arguments: <String, dynamic>{'uri': '/', 'state': null, 'replace': false},
95 ),
96 );
97 });
98
99 testWidgets('Navigator does not report route name by default', (WidgetTester tester) async {
100 final List<MethodCall> log = <MethodCall>[];
101 tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (
102 MethodCall methodCall,
103 ) async {
104 log.add(methodCall);
105 return null;
106 });
107
108 await tester.pumpWidget(
109 Directionality(
110 textDirection: TextDirection.ltr,
111 child: Navigator(
112 pages: const <Page<void>>[TestPage(name: '/')],
113 onPopPage: (Route<void> route, void result) => false,
114 ),
115 ),
116 );
117
118 expect(log, hasLength(0));
119
120 await tester.pumpWidget(
121 Directionality(
122 textDirection: TextDirection.ltr,
123 child: Navigator(
124 pages: const <Page<void>>[TestPage(name: '/'), TestPage(name: '/abc')],
125 onPopPage: (Route<void> route, void result) => false,
126 ),
127 ),
128 );
129
130 await tester.pumpAndSettle();
131 expect(log, hasLength(0));
132 });
133
134 testWidgets('Replace should send platform messages', (WidgetTester tester) async {
135 final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
136 '/':
137 (BuildContext context) => OnTapPage(
138 id: '/',
139 onTap: () {
140 Navigator.pushNamed(context, '/A');
141 },
142 ),
143 '/A':
144 (BuildContext context) => OnTapPage(
145 id: 'A',
146 onTap: () {
147 Navigator.pushReplacementNamed(context, '/B');
148 },
149 ),
150 '/B': (BuildContext context) => OnTapPage(id: 'B', onTap: () {}),
151 };
152
153 final List<MethodCall> log = <MethodCall>[];
154
155 tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (
156 MethodCall methodCall,
157 ) async {
158 log.add(methodCall);
159 return null;
160 });
161
162 await tester.pumpWidget(MaterialApp(routes: routes));
163
164 expect(log, <Object>[
165 isMethodCall('selectSingleEntryHistory', arguments: null),
166 isMethodCall(
167 'routeInformationUpdated',
168 arguments: <String, dynamic>{'uri': '/', 'state': null, 'replace': false},
169 ),
170 ]);
171 log.clear();
172
173 await tester.tap(find.text('/'));
174 await tester.pump();
175 await tester.pump(const Duration(seconds: 1));
176
177 expect(log, hasLength(1));
178 expect(
179 log.last,
180 isMethodCall(
181 'routeInformationUpdated',
182 arguments: <String, dynamic>{'uri': '/A', 'state': null, 'replace': false},
183 ),
184 );
185 log.clear();
186
187 await tester.tap(find.text('A'));
188 await tester.pump();
189 await tester.pump(const Duration(seconds: 1));
190
191 expect(log, hasLength(1));
192 expect(
193 log.last,
194 isMethodCall(
195 'routeInformationUpdated',
196 arguments: <String, dynamic>{'uri': '/B', 'state': null, 'replace': false},
197 ),
198 );
199 });
200
201 testWidgets('Nameless routes should send platform messages', (WidgetTester tester) async {
202 final List<MethodCall> log = <MethodCall>[];
203 tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (
204 MethodCall methodCall,
205 ) async {
206 log.add(methodCall);
207 return null;
208 });
209
210 await tester.pumpWidget(
211 MaterialApp(
212 initialRoute: '/home',
213 routes: <String, WidgetBuilder>{
214 '/home': (BuildContext context) {
215 return OnTapPage(
216 id: 'Home',
217 onTap: () {
218 // Create a route with no name.
219 final Route<void> route = MaterialPageRoute<void>(
220 builder: (BuildContext context) => const Text('Nameless Route'),
221 );
222 Navigator.push<void>(context, route);
223 },
224 );
225 },
226 },
227 ),
228 );
229
230 expect(log, <Object>[
231 isMethodCall('selectSingleEntryHistory', arguments: null),
232 isMethodCall(
233 'routeInformationUpdated',
234 arguments: <String, dynamic>{'uri': '/home', 'state': null, 'replace': false},
235 ),
236 ]);
237 log.clear();
238
239 await tester.tap(find.text('Home'));
240 await tester.pump();
241 await tester.pump(const Duration(seconds: 1));
242
243 expect(log, isEmpty);
244 });
245
246 testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async {
247 final List<MethodCall> log = <MethodCall>[];
248 tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (
249 MethodCall methodCall,
250 ) async {
251 log.add(methodCall);
252 return null;
253 });
254
255 final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
256 initialRouteInformation: RouteInformation(uri: Uri.parse('initial')),
257 );
258 addTearDown(provider.dispose);
259 final SimpleRouterDelegate delegate = SimpleRouterDelegate(
260 reportConfiguration: true,
261 builder: (BuildContext context, RouteInformation information) {
262 return Text(information.uri.toString());
263 },
264 );
265 addTearDown(delegate.dispose);
266
267 await tester.pumpWidget(
268 MaterialApp.router(
269 routeInformationProvider: provider,
270 routeInformationParser: SimpleRouteInformationParser(),
271 routerDelegate: delegate,
272 ),
273 );
274 expect(find.text('initial'), findsOneWidget);
275 expect(log, <Object>[
276 isMethodCall('selectMultiEntryHistory', arguments: null),
277 isMethodCall(
278 'routeInformationUpdated',
279 arguments: <String, dynamic>{'uri': 'initial', 'state': null, 'replace': false},
280 ),
281 ]);
282 log.clear();
283
284 // Triggers a router rebuild and verify the route information is reported
285 // to the web engine.
286 delegate.routeInformation = RouteInformation(uri: Uri.parse('update'), state: 'state');
287 await tester.pump();
288 expect(find.text('update'), findsOneWidget);
289
290 expect(log, <Object>[
291 isMethodCall('selectMultiEntryHistory', arguments: null),
292 isMethodCall(
293 'routeInformationUpdated',
294 arguments: <String, dynamic>{'uri': 'update', 'state': 'state', 'replace': false},
295 ),
296 ]);
297 });
298}
299
300typedef SimpleRouterDelegateBuilder =
301 Widget Function(BuildContext context, RouteInformation information);
302typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
303
304class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
305 SimpleRouteInformationParser();
306
307 @override
308 Future<RouteInformation> parseRouteInformation(RouteInformation information) {
309 return SynchronousFuture<RouteInformation>(information);
310 }
311
312 @override
313 RouteInformation restoreRouteInformation(RouteInformation configuration) {
314 return configuration;
315 }
316}
317
318class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
319 SimpleRouterDelegate({required this.builder, this.onPopRoute, this.reportConfiguration = false}) {
320 if (kFlutterMemoryAllocationsEnabled) {
321 ChangeNotifier.maybeDispatchObjectCreation(this);
322 }
323 }
324
325 RouteInformation get routeInformation => _routeInformation;
326 late RouteInformation _routeInformation;
327 set routeInformation(RouteInformation newValue) {
328 _routeInformation = newValue;
329 notifyListeners();
330 }
331
332 SimpleRouterDelegateBuilder builder;
333 SimpleRouterDelegatePopRoute? onPopRoute;
334 final bool reportConfiguration;
335
336 @override
337 RouteInformation? get currentConfiguration {
338 if (reportConfiguration) {
339 return routeInformation;
340 }
341 return null;
342 }
343
344 @override
345 Future<void> setNewRoutePath(RouteInformation configuration) {
346 _routeInformation = configuration;
347 return SynchronousFuture<void>(null);
348 }
349
350 @override
351 Future<bool> popRoute() => onPopRoute?.call() ?? SynchronousFuture<bool>(true);
352
353 @override
354 Widget build(BuildContext context) => builder(context, routeInformation);
355}
356
357class TestPage extends Page<void> {
358 const TestPage({super.key, super.name});
359
360 @override
361 Route<void> createRoute(BuildContext context) {
362 return PageRouteBuilder<void>(
363 settings: this,
364 pageBuilder:
365 (
366 BuildContext context,
367 Animation<double> animation,
368 Animation<double> secondaryAnimation,
369 ) => const Placeholder(),
370 );
371 }
372}
373

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com