| // 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:collection/collection.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| |
| void main() { |
| // Disconnects semantics listener for testing purposes. |
| // If the test passes, LifeCycleSpy will rewire the semantics listener back. |
| SwitchableSemanticsBinding.ensureInitialized(); |
| assert(!SwitchableSemanticsBinding.instance.semanticsEnabled); |
| |
| runApp(const LifeCycleSpy()); |
| } |
| |
| /// A Test widget that spies on app life cycle changes. |
| /// |
| /// It will collect the AppLifecycleState sequence during its lifetime, and it |
| /// will rewire semantics harness if the sequence it receives matches the |
| /// expected list. |
| /// |
| /// Rewiring semantics is a signal to native IOS test that the test has passed. |
| class LifeCycleSpy extends StatefulWidget { |
| const LifeCycleSpy({super.key}); |
| |
| @override |
| State<LifeCycleSpy> createState() => _LifeCycleSpyState(); |
| } |
| |
| class _LifeCycleSpyState extends State<LifeCycleSpy> with WidgetsBindingObserver { |
| final List<AppLifecycleState> _expectedLifeCycleSequence = <AppLifecycleState>[ |
| AppLifecycleState.detached, |
| AppLifecycleState.inactive, |
| AppLifecycleState.resumed, |
| ]; |
| List<AppLifecycleState?>? _actualLifeCycleSequence; |
| |
| @override |
| void initState() { |
| super.initState(); |
| WidgetsBinding.instance.addObserver(this); |
| _actualLifeCycleSequence = <AppLifecycleState?>[ |
| ServicesBinding.instance.lifecycleState, |
| ]; |
| } |
| |
| @override |
| void dispose() { |
| WidgetsBinding.instance.removeObserver(this); |
| super.dispose(); |
| } |
| |
| @override |
| void didChangeAppLifecycleState(AppLifecycleState state) { |
| setState(() { |
| _actualLifeCycleSequence = List<AppLifecycleState>.from(_actualLifeCycleSequence!); |
| _actualLifeCycleSequence?.add(state); |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| if (const ListEquality<AppLifecycleState?>().equals(_actualLifeCycleSequence, _expectedLifeCycleSequence)) { |
| // Rewires the semantics harness if test passes. |
| SwitchableSemanticsBinding.instance.semanticsEnabled = true; |
| } |
| return const MaterialApp( |
| title: 'Flutter View', |
| home: Text('test'), |
| ); |
| } |
| } |
| |
| class SwitchableSemanticsBinding extends WidgetsFlutterBinding { |
| static SwitchableSemanticsBinding get instance => BindingBase.checkInstance(_instance); |
| static SwitchableSemanticsBinding? _instance; |
| |
| static SwitchableSemanticsBinding ensureInitialized() { |
| if (_instance == null) { |
| SwitchableSemanticsBinding(); |
| } |
| return SwitchableSemanticsBinding.instance; |
| } |
| |
| VoidCallback? _originalSemanticsListener; |
| |
| @override |
| void initInstances() { |
| super.initInstances(); |
| _instance = this; |
| _updateHandler(); |
| } |
| |
| @override |
| bool get semanticsEnabled => _semanticsEnabled.value; |
| final ValueNotifier<bool> _semanticsEnabled = ValueNotifier<bool>(false); |
| set semanticsEnabled(bool value) { |
| _semanticsEnabled.value = value; |
| _updateHandler(); |
| } |
| |
| void _updateHandler() { |
| if (_semanticsEnabled.value) { |
| platformDispatcher.onSemanticsEnabledChanged = _originalSemanticsListener; |
| _originalSemanticsListener = null; |
| } else { |
| _originalSemanticsListener = platformDispatcher.onSemanticsEnabledChanged; |
| platformDispatcher.onSemanticsEnabledChanged = null; |
| } |
| } |
| |
| @override |
| void addSemanticsEnabledListener(VoidCallback listener) { |
| _semanticsEnabled.addListener(listener); |
| } |
| |
| @override |
| void removeSemanticsEnabledListener(VoidCallback listener) { |
| _semanticsEnabled.removeListener(listener); |
| } |
| } |