blob: 5001259352396f66852239f879e054c08487285a [file] [log] [blame]
// 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';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
void main() {
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();
listener = null;
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
binding.resetInternalState();
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 from lower to higher lifecycle 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']));
// Generates intermediate states from higher to lower lifecycle states.
transitions.clear();
await setAppLifeCycleState(AppLifecycleState.resumed);
expect(transitions, equals(<String>['restart', 'show', 'resume']));
// Go to detached
transitions.clear();
await setAppLifeCycleState(AppLifecycleState.detached);
expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach']));
});
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);
});
test('AppLifecycleListener dispatches memory events', () async {
await expectLater(
await memoryEvents(
() => AppLifecycleListener(binding: WidgetsBinding.instance).dispose(),
AppLifecycleListener,
),
areCreateAndDispose,
);
});
}
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;
}
}