forked from firka/flutter
Fix stuck predictive back platform channel calls (#133368)
Fix a Google test flakiness increase.
This commit is contained in:
committed by
GitHub
parent
69f61a289e
commit
dfd4147cea
@@ -1346,12 +1346,18 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
/// the platform with [NavigationNotification.canHandlePop] and stops
|
||||
/// bubbling.
|
||||
bool _defaultOnNavigationNotification(NavigationNotification notification) {
|
||||
// Don't do anything with navigation notifications if there is no engine
|
||||
// attached.
|
||||
if (_appLifecycleState != AppLifecycleState.detached) {
|
||||
SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop);
|
||||
switch (_appLifecycleState) {
|
||||
case null:
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.inactive:
|
||||
// Avoid updating the engine when the app isn't ready.
|
||||
return true;
|
||||
case AppLifecycleState.resumed:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.paused:
|
||||
SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1366,6 +1372,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
_updateRouting();
|
||||
_locale = _resolveLocales(WidgetsBinding.instance.platformDispatcher.locales, widget.supportedLocales);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_appLifecycleState = WidgetsBinding.instance.lifecycleState;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1219,11 +1219,7 @@ void main() {
|
||||
|
||||
group('Android Predictive Back', () {
|
||||
bool? lastFrameworkHandlesBack;
|
||||
setUp(() {
|
||||
// Initialize to false. Because this uses a static boolean internally, it
|
||||
// is not reset between tests or calls to pumpWidget. Explicitly setting
|
||||
// it to false before each test makes them behave deterministically.
|
||||
SystemNavigator.setFrameworkHandlesBack(false);
|
||||
setUp(() async {
|
||||
lastFrameworkHandlesBack = null;
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
@@ -1233,12 +1229,17 @@ void main() {
|
||||
}
|
||||
return;
|
||||
});
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.handlePlatformMessage(
|
||||
'flutter/lifecycle',
|
||||
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
SystemNavigator.setFrameworkHandlesBack(true);
|
||||
});
|
||||
|
||||
testWidgets('System back navigation inside of tabs', (WidgetTester tester) async {
|
||||
|
||||
@@ -683,6 +683,90 @@ void main() {
|
||||
expect(copySpy.invoked, isTrue);
|
||||
expect(pasteSpy.invoked, isTrue);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
group('Android Predictive Back', () {
|
||||
Future<void> setAppLifeCycleState(AppLifecycleState state) async {
|
||||
final ByteData? message = const StringCodec().encodeMessage(state.toString());
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.handlePlatformMessage('flutter/lifecycle', message, (ByteData? data) {});
|
||||
}
|
||||
|
||||
final List<bool> frameworkHandlesBacks = <bool>[];
|
||||
setUp(() async {
|
||||
frameworkHandlesBacks.clear();
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
|
||||
expect(methodCall.arguments, isA<bool>());
|
||||
frameworkHandlesBacks.add(methodCall.arguments as bool);
|
||||
}
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
await setAppLifeCycleState(AppLifecycleState.resumed);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp calls setFrameworkHandlesBack only when app is ready', (WidgetTester tester) async {
|
||||
// Start in the `resumed` state, where setFrameworkHandlesBack should be
|
||||
// called like normal.
|
||||
await setAppLifeCycleState(AppLifecycleState.resumed);
|
||||
|
||||
late BuildContext currentContext;
|
||||
await tester.pumpWidget(
|
||||
WidgetsApp(
|
||||
color: const Color(0xFF123456),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
currentContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(frameworkHandlesBacks, isEmpty);
|
||||
|
||||
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks, isNotEmpty);
|
||||
expect(frameworkHandlesBacks.last, isTrue);
|
||||
|
||||
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks.last, isFalse);
|
||||
|
||||
// Set the app state to inactive, where setFrameworkHandlesBack shouldn't
|
||||
// be called.
|
||||
await setAppLifeCycleState(AppLifecycleState.inactive);
|
||||
|
||||
final int finalCallsLength = frameworkHandlesBacks.length;
|
||||
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
|
||||
|
||||
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
|
||||
|
||||
// Set the app state to detached, which also shouldn't call
|
||||
// setFrameworkHandlesBack. Must go to paused, then detached.
|
||||
await setAppLifeCycleState(AppLifecycleState.paused);
|
||||
await setAppLifeCycleState(AppLifecycleState.detached);
|
||||
|
||||
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
|
||||
|
||||
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
|
||||
await tester.pumpAndSettle();
|
||||
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
|
||||
},
|
||||
skip: kIsWeb, // [intended] predictive back is only native Android.
|
||||
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
|
||||
@@ -4159,11 +4159,7 @@ void main() {
|
||||
|
||||
group('Android Predictive Back', () {
|
||||
bool? lastFrameworkHandlesBack;
|
||||
setUp(() {
|
||||
// Initialize to false. Because this uses a static boolean internally, it
|
||||
// is not reset between tests or calls to pumpWidget. Explicitly setting
|
||||
// it to false before each test makes them behave deterministically.
|
||||
SystemNavigator.setFrameworkHandlesBack(false);
|
||||
setUp(() async {
|
||||
lastFrameworkHandlesBack = null;
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
@@ -4173,12 +4169,17 @@ void main() {
|
||||
}
|
||||
return;
|
||||
});
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.handlePlatformMessage(
|
||||
'flutter/lifecycle',
|
||||
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
SystemNavigator.setFrameworkHandlesBack(true);
|
||||
});
|
||||
|
||||
testWidgets('a single route is already defaulted to false', (WidgetTester tester) async {
|
||||
|
||||
@@ -11,11 +11,7 @@ import 'navigator_utils.dart';
|
||||
|
||||
void main() {
|
||||
bool? lastFrameworkHandlesBack;
|
||||
setUp(() {
|
||||
// Initialize to false. Because this uses a static boolean internally, it
|
||||
// is not reset between tests or calls to pumpWidget. Explicitly setting
|
||||
// it to false before each test makes them behave deterministically.
|
||||
SystemNavigator.setFrameworkHandlesBack(false);
|
||||
setUp(() async {
|
||||
lastFrameworkHandlesBack = null;
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
@@ -25,12 +21,17 @@ void main() {
|
||||
}
|
||||
return;
|
||||
});
|
||||
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.handlePlatformMessage(
|
||||
'flutter/lifecycle',
|
||||
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
|
||||
(ByteData? data) {},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, null);
|
||||
SystemNavigator.setFrameworkHandlesBack(true);
|
||||
});
|
||||
|
||||
testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async {
|
||||
|
||||
Reference in New Issue
Block a user