| // 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 'dart:ui'; |
| |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| late AppLifecycleListener listener; |
| |
| Future<void> setAppLifeCycleState(AppLifecycleState state) async { |
| final ByteData? message = const StringCodec().encodeMessage(state.toString()); |
| await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| .handlePlatformMessage('flutter/lifecycle', message, (_) {}); |
| } |
| |
| Future<void> sendAppExitRequest() async { |
| final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('System.requestAppExit')); |
| await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| .handlePlatformMessage('flutter/platform', message, (_) {}); |
| } |
| |
| setUp(() async { |
| WidgetsFlutterBinding.ensureInitialized(); |
| WidgetsBinding.instance |
| ..resetEpoch() |
| ..platformDispatcher.onBeginFrame = null |
| ..platformDispatcher.onDrawFrame = null; |
| final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance; |
| binding.readTestInitialLifecycleStateFromNativeWindow(); |
| // Reset the state to detached. Going to paused first makes it a valid |
| // transition from any state, since the intermediate transitions will be |
| // generated. |
| await setAppLifeCycleState(AppLifecycleState.paused); |
| await setAppLifeCycleState(AppLifecycleState.detached); |
| }); |
| |
| tearDown(() { |
| listener.dispose(); |
| final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance; |
| binding.resetLifecycleState(); |
| binding.platformDispatcher.resetInitialLifecycleState(); |
| assert(TestAppLifecycleListener.registerCount == 0, |
| 'There were ${TestAppLifecycleListener.registerCount} listeners that were not disposed of in tests.'); |
| }); |
| |
| testWidgets('Default Diagnostics', (WidgetTester tester) async { |
| listener = TestAppLifecycleListener(binding: tester.binding); |
| expect(listener.toString(), |
| equalsIgnoringHashCodes('TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>)')); |
| }); |
| |
| testWidgets('Diagnostics', (WidgetTester tester) async { |
| Future<AppExitResponse> handleExitRequested() async { |
| return AppExitResponse.cancel; |
| } |
| |
| listener = TestAppLifecycleListener( |
| binding: WidgetsBinding.instance, |
| onExitRequested: handleExitRequested, |
| onStateChange: (AppLifecycleState _) {}, |
| ); |
| expect( |
| listener.toString(), |
| equalsIgnoringHashCodes( |
| 'TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>, onStateChange, onExitRequested)')); |
| }); |
| |
| testWidgets('listens to AppLifecycleState', (WidgetTester tester) async { |
| final List<AppLifecycleState> states = <AppLifecycleState>[tester.binding.lifecycleState!]; |
| void stateChange(AppLifecycleState state) { |
| states.add(state); |
| } |
| |
| listener = TestAppLifecycleListener( |
| binding: WidgetsBinding.instance, |
| onStateChange: stateChange, |
| ); |
| expect(states, equals(<AppLifecycleState>[AppLifecycleState.detached])); |
| await setAppLifeCycleState(AppLifecycleState.inactive); |
| // "resumed" is generated. |
| expect(states, |
| equals(<AppLifecycleState>[AppLifecycleState.detached, AppLifecycleState.resumed, AppLifecycleState.inactive])); |
| await setAppLifeCycleState(AppLifecycleState.resumed); |
| expect( |
| states, |
| equals(<AppLifecycleState>[ |
| AppLifecycleState.detached, |
| AppLifecycleState.resumed, |
| AppLifecycleState.inactive, |
| AppLifecycleState.resumed |
| ])); |
| }); |
| |
| testWidgets('Triggers correct state transition callbacks', (WidgetTester tester) async { |
| final List<String> transitions = <String>[]; |
| listener = TestAppLifecycleListener( |
| binding: WidgetsBinding.instance, |
| onDetach: () => transitions.add('detach'), |
| onHide: () => transitions.add('hide'), |
| onInactive: () => transitions.add('inactive'), |
| onPause: () => transitions.add('pause'), |
| onRestart: () => transitions.add('restart'), |
| onResume: () => transitions.add('resume'), |
| onShow: () => transitions.add('show'), |
| ); |
| |
| // Try "standard" sequence |
| await setAppLifeCycleState(AppLifecycleState.resumed); |
| expect(transitions, equals(<String>['resume'])); |
| await setAppLifeCycleState(AppLifecycleState.inactive); |
| expect(transitions, equals(<String>['resume', 'inactive'])); |
| await setAppLifeCycleState(AppLifecycleState.hidden); |
| expect(transitions, equals(<String>['resume', 'inactive', 'hide'])); |
| await setAppLifeCycleState(AppLifecycleState.paused); |
| expect(transitions, equals(<String>['resume', 'inactive', 'hide', 'pause'])); |
| |
| // Go back to resume |
| transitions.clear(); |
| await setAppLifeCycleState(AppLifecycleState.hidden); |
| expect(transitions, equals(<String>['restart'])); |
| await setAppLifeCycleState(AppLifecycleState.inactive); |
| expect(transitions, equals(<String>['restart', 'show'])); |
| await setAppLifeCycleState(AppLifecycleState.resumed); |
| expect(transitions, equals(<String>['restart', 'show', 'resume'])); |
| |
| // Generates intermediate states. |
| transitions.clear(); |
| await setAppLifeCycleState(AppLifecycleState.paused); |
| expect(transitions, equals(<String>['inactive', 'hide', 'pause'])); |
| // Wraps around from pause to detach. |
| await setAppLifeCycleState(AppLifecycleState.detached); |
| expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach'])); |
| await setAppLifeCycleState(AppLifecycleState.resumed); |
| expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach', 'resume'])); |
| await setAppLifeCycleState(AppLifecycleState.paused); |
| expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach', 'resume', 'inactive', 'hide', 'pause'])); |
| transitions.clear(); |
| await setAppLifeCycleState(AppLifecycleState.resumed); |
| expect(transitions, equals(<String>['restart', 'show', 'resume'])); |
| |
| // Asserts on bad transitions |
| await expectLater(() => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError); |
| await setAppLifeCycleState(AppLifecycleState.paused); |
| await setAppLifeCycleState(AppLifecycleState.detached); |
| }); |
| |
| testWidgets('Receives exit requests', (WidgetTester tester) async { |
| bool exitRequested = false; |
| Future<AppExitResponse> handleExitRequested() async { |
| exitRequested = true; |
| return AppExitResponse.cancel; |
| } |
| |
| listener = TestAppLifecycleListener( |
| binding: WidgetsBinding.instance, |
| onExitRequested: handleExitRequested, |
| ); |
| await sendAppExitRequest(); |
| expect(exitRequested, isTrue); |
| }); |
| } |
| |
| class TestAppLifecycleListener extends AppLifecycleListener { |
| TestAppLifecycleListener({ |
| super.binding, |
| super.onResume, |
| super.onInactive, |
| super.onHide, |
| super.onShow, |
| super.onPause, |
| super.onRestart, |
| super.onDetach, |
| super.onExitRequested, |
| super.onStateChange, |
| }) { |
| registerCount += 1; |
| } |
| |
| static int registerCount = 0; |
| |
| @override |
| void dispose() { |
| super.dispose(); |
| registerCount -= 1; |
| } |
| } |