Unverified Commit 5e4e530e authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix a legacy TODO (#77454)

parent 97ddb506
......@@ -3,8 +3,10 @@
// found in the LICENSE file.
// @dart = 2.9
import 'dart:html';
import 'dart:js_util' as js_util;
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -21,9 +23,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget);
......@@ -49,9 +48,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField.
final Finder finder = find.byKey(const Key('empty-input'));
expect(finder, findsOneWidget);
......@@ -77,9 +73,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// This text will show no-enter initially. It will have 'enter-pressed'
// after `onFieldSubmitted` of TextField is triggered.
final Finder textFinder = find.byKey(const Key('text'));
......@@ -113,9 +106,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget);
......@@ -148,9 +138,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget);
......@@ -198,9 +185,6 @@ void main() {
app.main();
await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Select something from the selectable text.
final Finder finder = find.byKey(const Key('selectable'));
expect(finder, findsOneWidget);
......
......@@ -195,9 +195,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// Called by the test framework at the beginning of a widget test to
/// prepare the binding for the next test.
///
/// If [registerTestTextInput] returns true when this method is called,
/// the [testTextInput] is configured to simulate the keyboard.
void reset() {
_restorationManager = null;
resetGestureBinding();
testTextInput.reset();
if (registerTestTextInput)
_testTextInput.register();
}
@override
......@@ -237,7 +243,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected
bool get overrideHttpClient => true;
/// Determines whether the binding automatically registers [testTextInput].
/// Determines whether the binding automatically registers [testTextInput] as
/// a fake keyboard implementation.
///
/// Unit tests make use of this to mock out text input communication for
/// widgets. An integration test would set this to false, to test real IME
......@@ -245,6 +252,19 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
///
/// [TestTextInput.isRegistered] reports whether the text input mock is
/// registered or not.
///
/// Some of the properties and methods on [testTextInput] are only valid if
/// [registerTestTextInput] returns true when a test starts. If those
/// members are accessed when using a binding that sets this flag to false,
/// they will throw.
///
/// If this property returns true when a test ends, the [testTextInput] is
/// unregistered.
///
/// This property should not change the value it returns during the lifetime
/// of the binding. Changing the value of this property risks very confusing
/// behavior as the [TestTextInput] may be inconsistently registered or
/// unregistered.
@protected
bool get registerTestTextInput => true;
......@@ -319,9 +339,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
binding.setupHttpOverrides();
}
_testTextInput = TestTextInput(onCleared: _resetFocusedEditable);
if (registerTestTextInput) {
_testTextInput.register();
}
}
@override
......@@ -799,6 +816,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// alone so that we don't cause more spurious errors.
runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets.
await pump();
if (registerTestTextInput)
_testTextInput.unregister();
invariantTester();
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
......
......@@ -15,6 +15,18 @@ export 'package:flutter/services.dart' show TextEditingValue, TextInputAction;
///
/// Typical app tests will not need to use this class directly.
///
/// The [TestWidgetsFlutterBinding] class registers a [TestTextInput] instance
/// ([TestWidgetsFlutterBinding.testTextInput]) as a stub keyboard
/// implementation if its [TestWidgetsFlutterBinding.registerTestTextInput]
/// property returns true when a test starts, and unregisters it when the test
/// ends (unless it ends with a failure).
///
/// See [register], [unregister], and [isRegistered] for details.
///
/// The [updateEditingValue], [enterText], and [receiveAction] methods can be
/// used even when the [TestTextInput] is not registered. All other methods
/// will assert if [isRegistered] is false.
///
/// See also:
///
/// * [WidgetTester.enterText], which uses this class to simulate keyboard input.
......@@ -37,37 +49,43 @@ class TestTextInput {
/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger get _binaryMessenger => ServicesBinding.instance!.defaultBinaryMessenger;
/// Resets any internal state of this object and calls [register].
/// Log for method calls.
///
/// For all registered channels, handled calls are added to the list. Can
/// be cleaned using `log.clear()`.
final List<MethodCall> log = <MethodCall>[];
/// Resets any internal state of this object.
///
/// This method is invoked by the testing framework between tests. It should
/// not ordinarily be called by tests directly.
void resetAndRegister() {
void reset() {
log.clear();
editingState = null;
setClientArgs = null;
_client = 0;
_isVisible = false;
register();
}
/// Installs this object as a mock handler for [SystemChannels.textInput].
///
/// Called by the binding at the top of a test when
/// [TestWidgetsFlutterBinding.registerTestTextInput] is true.
void register() => SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
/// Removes this object as a mock handler for [SystemChannels.textInput].
///
/// After calling this method, the channel will exchange messages with the
/// Flutter engine. Use this with [FlutterDriver] tests that need to display
/// on-screen keyboard provided by the operating system.
void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null);
/// Log for method calls.
/// Flutter engine instead of the stub.
///
/// For all registered channels, handled calls are added to the list. Can
/// be cleaned using `log.clear()`.
final List<MethodCall> log = <MethodCall>[];
/// Called by the binding at the end of a (successful) test when
/// [TestWidgetsFlutterBinding.registerTestTextInput] is true.
void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null);
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
///
/// Use [register] and [unregister] methods to control this value.
/// The binding uses the [register] and [unregister] methods to control this
/// value when [TestWidgetsFlutterBinding.registerTestTextInput] is true.
bool get isRegistered => SystemChannels.textInput.checkMockMethodCallHandler(_handleTextInputCall);
/// Whether there are any active clients listening to text input.
......@@ -78,11 +96,13 @@ class TestTextInput {
int _client = 0;
/// Arguments supplied to the TextInput.setClient method call.
/// The last set of arguments supplied to the `TextInput.setClient` and
/// `TextInput.updateConfig` methods of this stub implementation.
Map<String, dynamic>? setClientArgs;
/// The last set of arguments that [TextInputConnection.setEditingState] sent
/// to the embedder.
/// to this stub implementation (i.e. the arguments set to
/// `TextInput.setEditingState`).
///
/// This is a map representation of a [TextEditingValue] object. For example,
/// it will have a `text` entry whose value matches the most recent
......@@ -118,15 +138,27 @@ class TestTextInput {
}
/// Whether the onscreen keyboard is visible to the user.
///
/// Specifically, this reflects the last call to `TextInput.show` or
/// `TextInput.hide` received by the stub implementation.
bool get isVisible {
assert(isRegistered);
return _isVisible;
}
bool _isVisible = false;
/// Simulates the user hiding the onscreen keyboard.
///
/// This does nothing but set the internal flag.
void hide() {
assert(isRegistered);
_isVisible = false;
}
/// Simulates the user changing the [TextEditingValue] to the given value.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
void updateEditingValue(TextEditingValue value) {
assert(isRegistered);
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
if (_client == 0)
......@@ -146,6 +178,7 @@ class TestTextInput {
/// Simulates the user closing the text input connection.
///
/// For example:
///
/// - User pressed the home button and sent the application to background.
/// - User closed the virtual keyboard.
void closeConnection() {
......@@ -167,8 +200,9 @@ class TestTextInput {
}
/// Simulates the user typing the given text.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
void enterText(String text) {
assert(isRegistered);
updateEditingValue(TextEditingValue(
text: text,
));
......@@ -177,8 +211,9 @@ class TestTextInput {
/// Simulates the user pressing one of the [TextInputAction] buttons.
/// Does not check that the [TextInputAction] performed is an acceptable one
/// based on the `inputAction` [setClientArgs].
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
Future<void> receiveAction(TextInputAction action) async {
assert(isRegistered);
return TestAsyncUtils.guard(() {
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
......@@ -216,10 +251,4 @@ class TestTextInput {
return completer.future;
});
}
/// Simulates the user hiding the onscreen keyboard.
void hide() {
assert(isRegistered);
_isVisible = false;
}
}
......@@ -136,7 +136,6 @@ void testWidgets(
() async {
binding.reset();
debugResetSemanticsIdCounter();
tester.resetTestTextInput();
Object? memento;
try {
memento = await variant.setUp(value);
......@@ -989,18 +988,13 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
///
/// Typical app tests will not need to use this value. To add text to widgets
/// like [TextField] or [TextFormField], call [enterText].
TestTextInput get testTextInput => binding.testTextInput;
/// Ensures that [testTextInput] is registered and [TestTextInput.log] is
/// reset.
///
/// This is called by the testing framework before test runs, so that if a
/// previous test has set its own handler on [SystemChannels.textInput], the
/// [testTextInput] regains control and the log is fresh for the new test.
/// It should not typically need to be called by tests.
void resetTestTextInput() {
testTextInput.resetAndRegister();
}
/// Some of the properties and methods on this value are only valid if the
/// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to
/// true as a test is starting (meaning that the keyboard is to be simulated
/// by the test framework). If those members are accessed when using a binding
/// that sets this flag to false, they will throw.
TestTextInput get testTextInput => binding.testTextInput;
/// Give the text input widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared.
......
......@@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:test_api/test_api.dart' as test_package;
void main() {
final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
group(TestViewConfiguration, () {
test('is initialized with top-level window if one is not provided', () {
// The code below will throw without the default.
......@@ -20,15 +22,32 @@ void main() {
group(AutomatedTestWidgetsFlutterBinding, () {
test('allows setting defaultTestTimeout to 5 minutes', () {
final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
binding.defaultTestTimeout = const test_package.Timeout(Duration(minutes: 5));
expect(binding.defaultTestTimeout.duration, const Duration(minutes: 5));
});
});
// The next three tests must run in order -- first using `test`, then `testWidgets`, then `test` again.
int order = 0;
test('Initializes httpOverrides and testTextInput', () async {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
expect(binding.testTextInput.isRegistered, true);
assert(order == 0);
expect(binding.testTextInput, isNotNull);
expect(binding.testTextInput.isRegistered, isFalse);
expect(HttpOverrides.current, isNotNull);
order += 1;
});
testWidgets('Registers testTextInput', (WidgetTester tester) async {
assert(order == 1);
expect(tester.testTextInput.isRegistered, isTrue);
order += 1;
});
test('Unregisters testTextInput', () async {
assert(order == 2);
expect(binding.testTextInput.isRegistered, isFalse);
order += 1;
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment