mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
364 lines
13 KiB
Dart
364 lines
13 KiB
Dart
// 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 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.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);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('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(),
|
|
);
|
|
|
|
testWidgetsWithLeakTracking('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(),
|
|
);
|
|
|
|
testWidgetsWithLeakTracking('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(),
|
|
);
|
|
|
|
testWidgetsWithLeakTracking('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(),
|
|
);
|
|
}
|