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
5import 'package:flutter/foundation.dart';
6import 'package:flutter/material.dart';
7import 'package:flutter/services.dart';
8import 'package:flutter_test/flutter_test.dart';
9
10import 'navigator_utils.dart';
11
12void main() {
13 bool? lastFrameworkHandlesBack;
14 setUp(() async {
15 lastFrameworkHandlesBack = null;
16 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
17 SystemChannels.platform,
18 (MethodCall methodCall) async {
19 if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
20 expect(methodCall.arguments, isA<bool>());
21 lastFrameworkHandlesBack = methodCall.arguments as bool;
22 }
23 return;
24 },
25 );
26 await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
27 'flutter/lifecycle',
28 const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
29 (ByteData? data) {},
30 );
31 });
32
33 tearDown(() {
34 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
35 SystemChannels.platform,
36 null,
37 );
38 });
39
40 testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async {
41 bool canPop = false;
42 late StateSetter setState;
43 late BuildContext context;
44 await tester.pumpWidget(
45 MaterialApp(
46 initialRoute: '/',
47 routes: <String, WidgetBuilder>{
48 '/':
49 (BuildContext buildContext) => Scaffold(
50 body: StatefulBuilder(
51 builder: (BuildContext buildContext, StateSetter stateSetter) {
52 context = buildContext;
53 setState = stateSetter;
54 return PopScope<Object?>(
55 canPop: canPop,
56 child: const Center(
57 child: Column(
58 mainAxisAlignment: MainAxisAlignment.center,
59 children: <Widget>[Text('Home/PopScope Page')],
60 ),
61 ),
62 );
63 },
64 ),
65 ),
66 },
67 ),
68 );
69
70 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
71
72 setState(() {
73 canPop = true;
74 });
75 await tester.pump();
76 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
77 expect(lastFrameworkHandlesBack, isFalse);
78 }
79 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
80 }, variant: TargetPlatformVariant.all());
81
82 testWidgets('pop scope can receive result', (WidgetTester tester) async {
83 Object? receivedResult;
84 final Object poppedResult = Object();
85 final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
86 await tester.pumpWidget(
87 MaterialApp(
88 initialRoute: '/',
89 navigatorKey: nav,
90 home: Scaffold(
91 body: PopScope<Object?>(
92 canPop: false,
93 onPopInvokedWithResult: (bool didPop, Object? result) {
94 receivedResult = result;
95 },
96 child: const Center(
97 child: Column(
98 mainAxisAlignment: MainAxisAlignment.center,
99 children: <Widget>[Text('Home/PopScope Page')],
100 ),
101 ),
102 ),
103 ),
104 ),
105 );
106
107 nav.currentState!.maybePop(poppedResult);
108 await tester.pumpAndSettle();
109 expect(receivedResult, poppedResult);
110 }, variant: TargetPlatformVariant.all());
111
112 testWidgets(
113 'pop scope can have Object? generic type while route has stricter generic type',
114 (WidgetTester tester) async {
115 Object? receivedResult;
116 const int poppedResult = 13;
117 final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
118 await tester.pumpWidget(
119 MaterialApp(
120 initialRoute: '/',
121 navigatorKey: nav,
122 home: Scaffold(
123 body: PopScope<Object?>(
124 canPop: false,
125 onPopInvokedWithResult: (bool didPop, Object? result) {
126 receivedResult = result;
127 },
128 child: const Center(
129 child: Column(
130 mainAxisAlignment: MainAxisAlignment.center,
131 children: <Widget>[Text('Home/PopScope Page')],
132 ),
133 ),
134 ),
135 ),
136 ),
137 );
138
139 nav.currentState!.push(
140 MaterialPageRoute<int>(
141 builder: (BuildContext context) {
142 return Scaffold(
143 body: PopScope<Object?>(
144 canPop: false,
145 onPopInvokedWithResult: (bool didPop, Object? result) {
146 receivedResult = result;
147 },
148 child: const Center(child: Text('new page')),
149 ),
150 );
151 },
152 ),
153 );
154 await tester.pumpAndSettle();
155 expect(find.text('new page'), findsOneWidget);
156
157 nav.currentState!.maybePop(poppedResult);
158 await tester.pumpAndSettle();
159 expect(receivedResult, poppedResult);
160 },
161 variant: TargetPlatformVariant.all(),
162 );
163
164 testWidgets('toggling canPop on secondary route allows/prevents backs', (
165 WidgetTester tester,
166 ) async {
167 final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
168 bool canPop = true;
169 late StateSetter setState;
170 late BuildContext homeContext;
171 late BuildContext oneContext;
172 late bool lastPopSuccess;
173 await tester.pumpWidget(
174 MaterialApp(
175 navigatorKey: nav,
176 initialRoute: '/',
177 routes: <String, WidgetBuilder>{
178 '/': (BuildContext context) {
179 homeContext = context;
180 return Scaffold(
181 body: Center(
182 child: Column(
183 mainAxisAlignment: MainAxisAlignment.center,
184 children: <Widget>[
185 const Text('Home Page'),
186 TextButton(
187 onPressed: () {
188 Navigator.of(context).pushNamed('/one');
189 },
190 child: const Text('Next'),
191 ),
192 ],
193 ),
194 ),
195 );
196 },
197 '/one':
198 (BuildContext context) => Scaffold(
199 body: StatefulBuilder(
200 builder: (BuildContext context, StateSetter stateSetter) {
201 oneContext = context;
202 setState = stateSetter;
203 return PopScope<Object?>(
204 canPop: canPop,
205 onPopInvokedWithResult: (bool didPop, Object? result) {
206 lastPopSuccess = didPop;
207 },
208 child: const Center(
209 child: Column(
210 mainAxisAlignment: MainAxisAlignment.center,
211 children: <Widget>[Text('PopScope Page')],
212 ),
213 ),
214 );
215 },
216 ),
217 ),
218 },
219 ),
220 );
221
222 expect(find.text('Home Page'), findsOneWidget);
223 expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
224
225 await tester.tap(find.text('Next'));
226 await tester.pumpAndSettle();
227 expect(find.text('PopScope Page'), findsOneWidget);
228 expect(find.text('Home Page'), findsNothing);
229 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
230 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
231 expect(lastFrameworkHandlesBack, isTrue);
232 }
233
234 // When canPop is true, can use pop to go back.
235 nav.currentState!.maybePop();
236 await tester.pumpAndSettle();
237 expect(lastPopSuccess, true);
238 expect(find.text('Home Page'), findsOneWidget);
239 expect(find.text('PopScope Page'), findsNothing);
240 expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
241 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
242 expect(lastFrameworkHandlesBack, isFalse);
243 }
244
245 await tester.tap(find.text('Next'));
246 await tester.pumpAndSettle();
247 expect(find.text('PopScope Page'), findsOneWidget);
248 expect(find.text('Home Page'), findsNothing);
249 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
250 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
251 expect(lastFrameworkHandlesBack, isTrue);
252 }
253
254 // When canPop is true, can use system back to go back.
255 await simulateSystemBack();
256 await tester.pumpAndSettle();
257 expect(lastPopSuccess, true);
258 expect(find.text('Home Page'), findsOneWidget);
259 expect(find.text('PopScope Page'), findsNothing);
260 expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
261 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
262 expect(lastFrameworkHandlesBack, isFalse);
263 }
264
265 await tester.tap(find.text('Next'));
266 await tester.pumpAndSettle();
267 expect(find.text('PopScope Page'), findsOneWidget);
268 expect(find.text('Home Page'), findsNothing);
269 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
270 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
271 expect(lastFrameworkHandlesBack, isTrue);
272 }
273
274 setState(() {
275 canPop = false;
276 });
277 await tester.pump();
278
279 // When canPop is false, can't use pop to go back.
280 nav.currentState!.maybePop();
281 await tester.pumpAndSettle();
282 expect(lastPopSuccess, false);
283 expect(find.text('PopScope Page'), findsOneWidget);
284 expect(find.text('Home Page'), findsNothing);
285 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop);
286
287 // When canPop is false, can't use system back to go back.
288 await simulateSystemBack();
289 await tester.pumpAndSettle();
290 expect(lastPopSuccess, false);
291 expect(find.text('PopScope Page'), findsOneWidget);
292 expect(find.text('Home Page'), findsNothing);
293 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop);
294
295 // Toggle canPop back to true and back works again.
296 setState(() {
297 canPop = true;
298 });
299 await tester.pump();
300
301 nav.currentState!.maybePop();
302 await tester.pumpAndSettle();
303 expect(lastPopSuccess, true);
304 expect(find.text('Home Page'), findsOneWidget);
305 expect(find.text('PopScope Page'), findsNothing);
306 expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
307 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
308 expect(lastFrameworkHandlesBack, isFalse);
309 }
310
311 await tester.tap(find.text('Next'));
312 await tester.pumpAndSettle();
313 expect(find.text('PopScope Page'), findsOneWidget);
314 expect(find.text('Home Page'), findsNothing);
315 expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
316 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
317 expect(lastFrameworkHandlesBack, isTrue);
318 }
319
320 await simulateSystemBack();
321 await tester.pumpAndSettle();
322 expect(lastPopSuccess, true);
323 expect(find.text('Home Page'), findsOneWidget);
324 expect(find.text('PopScope Page'), findsNothing);
325 expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
326 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
327 expect(lastFrameworkHandlesBack, isFalse);
328 }
329 }, variant: TargetPlatformVariant.all());
330
331 testWidgets(
332 'removing PopScope from the tree removes its effect on navigation',
333 (WidgetTester tester) async {
334 bool usePopScope = true;
335 late StateSetter setState;
336 late BuildContext context;
337 await tester.pumpWidget(
338 MaterialApp(
339 initialRoute: '/',
340 routes: <String, WidgetBuilder>{
341 '/':
342 (BuildContext buildContext) => Scaffold(
343 body: StatefulBuilder(
344 builder: (BuildContext buildContext, StateSetter stateSetter) {
345 context = buildContext;
346 setState = stateSetter;
347 const Widget child = Center(
348 child: Column(
349 mainAxisAlignment: MainAxisAlignment.center,
350 children: <Widget>[Text('Home/PopScope Page')],
351 ),
352 );
353 if (!usePopScope) {
354 return child;
355 }
356 return const PopScope<Object?>(canPop: false, child: child);
357 },
358 ),
359 ),
360 },
361 ),
362 );
363
364 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
365 expect(lastFrameworkHandlesBack, isTrue);
366 }
367 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
368
369 setState(() {
370 usePopScope = false;
371 });
372 await tester.pump();
373 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
374 expect(lastFrameworkHandlesBack, isFalse);
375 }
376 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
377 },
378 variant: TargetPlatformVariant.all(),
379 );
380
381 testWidgets('identical PopScopes', (WidgetTester tester) async {
382 bool usePopScope1 = true;
383 bool usePopScope2 = true;
384 late StateSetter setState;
385 late BuildContext context;
386 await tester.pumpWidget(
387 MaterialApp(
388 home: Scaffold(
389 body: StatefulBuilder(
390 builder: (BuildContext buildContext, StateSetter stateSetter) {
391 context = buildContext;
392 setState = stateSetter;
393 return Column(
394 children: <Widget>[
395 if (usePopScope1) const PopScope<Object?>(canPop: false, child: Text('hello')),
396 if (usePopScope2) const PopScope<Object?>(canPop: false, child: Text('hello')),
397 ],
398 );
399 },
400 ),
401 ),
402 ),
403 );
404
405 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
406 expect(lastFrameworkHandlesBack, isTrue);
407 }
408 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
409
410 // Despite being in the widget tree twice, the ModalRoute has only ever
411 // registered one PopScopeInterface for it. Removing one makes it think that
412 // both have been removed.
413 setState(() {
414 usePopScope1 = false;
415 });
416 await tester.pump();
417 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
418 expect(lastFrameworkHandlesBack, isTrue);
419 }
420 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
421
422 setState(() {
423 usePopScope2 = false;
424 });
425 await tester.pump();
426 if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
427 expect(lastFrameworkHandlesBack, isFalse);
428 }
429 expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
430 }, variant: TargetPlatformVariant.all());
431}
432

Provided by KDAB

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