| // Copyright 2014 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/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'navigator_utils.dart'; |
| |
| void main() { |
| bool? lastFrameworkHandlesBack; |
| setUp(() async { |
| lastFrameworkHandlesBack = null; |
| TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { |
| if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') { |
| expect(methodCall.arguments, isA<bool>()); |
| lastFrameworkHandlesBack = methodCall.arguments as bool; |
| } |
| return; |
| }); |
| await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| .handlePlatformMessage( |
| 'flutter/lifecycle', |
| const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()), |
| (ByteData? data) {}, |
| ); |
| }); |
| |
| tearDown(() { |
| TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| .setMockMethodCallHandler(SystemChannels.platform, null); |
| }); |
| |
| testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async { |
| bool canPop = false; |
| late StateSetter setState; |
| late BuildContext context; |
| await tester.pumpWidget( |
| MaterialApp( |
| initialRoute: '/', |
| routes: <String, WidgetBuilder>{ |
| '/': (BuildContext buildContext) => Scaffold( |
| body: StatefulBuilder( |
| builder: (BuildContext buildContext, StateSetter stateSetter) { |
| context = buildContext; |
| setState = stateSetter; |
| return PopScope( |
| canPop: canPop, |
| child: const Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text('Home/PopScope Page'), |
| ], |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| }, |
| ), |
| ); |
| |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| setState(() { |
| canPop = true; |
| }); |
| await tester.pump(); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble); |
| }, |
| variant: TargetPlatformVariant.all(), |
| ); |
| |
| testWidgets('toggling canPop on secondary route allows/prevents backs', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>(); |
| bool canPop = true; |
| late StateSetter setState; |
| late BuildContext homeContext; |
| late BuildContext oneContext; |
| late bool lastPopSuccess; |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: nav, |
| initialRoute: '/', |
| routes: <String, WidgetBuilder>{ |
| '/': (BuildContext context) { |
| homeContext = context; |
| return Scaffold( |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| const Text('Home Page'), |
| TextButton( |
| onPressed: () { |
| Navigator.of(context).pushNamed('/one'); |
| }, |
| child: const Text('Next'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| }, |
| '/one': (BuildContext context) => Scaffold( |
| body: StatefulBuilder( |
| builder: (BuildContext context, StateSetter stateSetter) { |
| oneContext = context; |
| setState = stateSetter; |
| return PopScope( |
| canPop: canPop, |
| onPopInvoked: (bool didPop) { |
| lastPopSuccess = didPop; |
| }, |
| child: const Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text('PopScope Page'), |
| ], |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| }, |
| ), |
| ); |
| |
| expect(find.text('Home Page'), findsOneWidget); |
| expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble); |
| |
| await tester.tap(find.text('Next')); |
| await tester.pumpAndSettle(); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| |
| // When canPop is true, can use pop to go back. |
| nav.currentState!.maybePop(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, true); |
| expect(find.text('Home Page'), findsOneWidget); |
| expect(find.text('PopScope Page'), findsNothing); |
| expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| |
| await tester.tap(find.text('Next')); |
| await tester.pumpAndSettle(); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| |
| // When canPop is true, can use system back to go back. |
| await simulateSystemBack(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, true); |
| expect(find.text('Home Page'), findsOneWidget); |
| expect(find.text('PopScope Page'), findsNothing); |
| expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| |
| await tester.tap(find.text('Next')); |
| await tester.pumpAndSettle(); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| |
| setState(() { |
| canPop = false; |
| }); |
| await tester.pump(); |
| |
| // When canPop is false, can't use pop to go back. |
| nav.currentState!.maybePop(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, false); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| // When canPop is false, can't use system back to go back. |
| await simulateSystemBack(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, false); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| // Toggle canPop back to true and back works again. |
| setState(() { |
| canPop = true; |
| }); |
| await tester.pump(); |
| |
| nav.currentState!.maybePop(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, true); |
| expect(find.text('Home Page'), findsOneWidget); |
| expect(find.text('PopScope Page'), findsNothing); |
| expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| |
| await tester.tap(find.text('Next')); |
| await tester.pumpAndSettle(); |
| expect(find.text('PopScope Page'), findsOneWidget); |
| expect(find.text('Home Page'), findsNothing); |
| expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| |
| await simulateSystemBack(); |
| await tester.pumpAndSettle(); |
| expect(lastPopSuccess, true); |
| expect(find.text('Home Page'), findsOneWidget); |
| expect(find.text('PopScope Page'), findsNothing); |
| expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| }, |
| variant: TargetPlatformVariant.all(), |
| ); |
| |
| testWidgets('removing PopScope from the tree removes its effect on navigation', (WidgetTester tester) async { |
| bool usePopScope = true; |
| late StateSetter setState; |
| late BuildContext context; |
| await tester.pumpWidget( |
| MaterialApp( |
| initialRoute: '/', |
| routes: <String, WidgetBuilder>{ |
| '/': (BuildContext buildContext) => Scaffold( |
| body: StatefulBuilder( |
| builder: (BuildContext buildContext, StateSetter stateSetter) { |
| context = buildContext; |
| setState = stateSetter; |
| const Widget child = Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text('Home/PopScope Page'), |
| ], |
| ), |
| ); |
| if (!usePopScope) { |
| return child; |
| } |
| return const PopScope( |
| canPop: false, |
| child: child, |
| ); |
| }, |
| ), |
| ), |
| }, |
| ), |
| ); |
| |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| setState(() { |
| usePopScope = false; |
| }); |
| await tester.pump(); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble); |
| }, |
| variant: TargetPlatformVariant.all(), |
| ); |
| |
| testWidgets('identical PopScopes', (WidgetTester tester) async { |
| bool usePopScope1 = true; |
| bool usePopScope2 = true; |
| late StateSetter setState; |
| late BuildContext context; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: StatefulBuilder( |
| builder: (BuildContext buildContext, StateSetter stateSetter) { |
| context = buildContext; |
| setState = stateSetter; |
| return Column( |
| children: <Widget>[ |
| if (usePopScope1) |
| const PopScope( |
| canPop: false, |
| child: Text('hello'), |
| ), |
| if (usePopScope2) |
| const PopScope( |
| canPop: false, |
| child: Text('hello'), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| // Despite being in the widget tree twice, the ModalRoute has only ever |
| // registered one PopScopeInterface for it. Removing one makes it think that |
| // both have been removed. |
| setState(() { |
| usePopScope1 = false; |
| }); |
| await tester.pump(); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isTrue); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop); |
| |
| setState(() { |
| usePopScope2 = false; |
| }); |
| await tester.pump(); |
| if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { |
| expect(lastFrameworkHandlesBack, isFalse); |
| } |
| expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble); |
| }, |
| variant: TargetPlatformVariant.all(), |
| ); |
| } |