Unverified Commit 0efb28d7 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Revert "Fix a legacy TODO (#77454) (#79061)" (#79959)

This reverts commit b070ed3c.
parent 9a7eae0c
...@@ -3,10 +3,8 @@ ...@@ -3,10 +3,8 @@
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.9 // @dart = 2.9
import 'dart:html'; import 'dart:html';
import 'dart:js_util' as js_util; import 'dart:js_util' as js_util;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -23,6 +21,9 @@ void main() { ...@@ -23,6 +21,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField. // Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input')); final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget); expect(finder, findsOneWidget);
...@@ -48,6 +49,9 @@ void main() { ...@@ -48,6 +49,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField. // Focus on a TextFormField.
final Finder finder = find.byKey(const Key('empty-input')); final Finder finder = find.byKey(const Key('empty-input'));
expect(finder, findsOneWidget); expect(finder, findsOneWidget);
...@@ -73,6 +77,9 @@ void main() { ...@@ -73,6 +77,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); 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' // This text will show no-enter initially. It will have 'enter-pressed'
// after `onFieldSubmitted` of TextField is triggered. // after `onFieldSubmitted` of TextField is triggered.
final Finder textFinder = find.byKey(const Key('text')); final Finder textFinder = find.byKey(const Key('text'));
...@@ -106,6 +113,9 @@ void main() { ...@@ -106,6 +113,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField. // Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input')); final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget); expect(finder, findsOneWidget);
...@@ -138,6 +148,9 @@ void main() { ...@@ -138,6 +148,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Focus on a TextFormField. // Focus on a TextFormField.
final Finder finder = find.byKey(const Key('input')); final Finder finder = find.byKey(const Key('input'));
expect(finder, findsOneWidget); expect(finder, findsOneWidget);
...@@ -185,6 +198,9 @@ void main() { ...@@ -185,6 +198,9 @@ void main() {
app.main(); app.main();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels.textInput.setMockMethodCallHandler(null);
// Select something from the selectable text. // Select something from the selectable text.
final Finder finder = find.byKey(const Key('selectable')); final Finder finder = find.byKey(const Key('selectable'));
expect(finder, findsOneWidget); expect(finder, findsOneWidget);
......
...@@ -120,11 +120,6 @@ class SystemChannels { ...@@ -120,11 +120,6 @@ class SystemChannels {
/// they apply, so that stale messages referencing past transactions can be /// they apply, so that stale messages referencing past transactions can be
/// ignored. /// ignored.
/// ///
/// In debug builds, messages sent with a client ID of -1 are always accepted.
/// This allows tests to smuggle messages without having to mock the engine's
/// text handling (for example, allowing the engine to still handle the text
/// input messages in an integration test).
///
/// The methods described below are wrapped in a more convenient form by the /// The methods described below are wrapped in a more convenient form by the
/// [TextInput] and [TextInputConnection] class. /// [TextInput] and [TextInputConnection] class.
/// ///
...@@ -157,15 +152,9 @@ class SystemChannels { ...@@ -157,15 +152,9 @@ class SystemChannels {
/// is a transaction identifier. Calls for stale transactions should be ignored. /// is a transaction identifier. Calls for stale transactions should be ignored.
/// ///
/// * `TextInputClient.updateEditingState`: The user has changed the contents /// * `TextInputClient.updateEditingState`: The user has changed the contents
/// of the text control. The second argument is an object with seven keys, /// of the text control. The second argument is a [String] containing a
/// in the form expected by [TextEditingValue.fromJSON]. /// JSON-encoded object with seven keys, in the form expected by
/// /// [TextEditingValue.fromJSON].
/// * `TextInputClient.updateEditingStateWithTag`: One or more text controls
/// were autofilled by the platform's autofill service. The first argument
/// (the client ID) is ignored, the second argument is a map of tags to
/// objects in the form expected by [TextEditingValue.fromJSON]. See
/// [AutofillScope.getAutofillClient] for details on the interpretation of
/// the tag.
/// ///
/// * `TextInputClient.performAction`: The user has triggered an action. The /// * `TextInputClient.performAction`: The user has triggered an action. The
/// second argument is a [String] consisting of the stringification of one /// second argument is a [String] consisting of the stringification of one
...@@ -176,8 +165,7 @@ class SystemChannels { ...@@ -176,8 +165,7 @@ class SystemChannels {
/// one. The framework should call `TextInput.setClient` and /// one. The framework should call `TextInput.setClient` and
/// `TextInput.setEditingState` again with its most recent information. If /// `TextInput.setEditingState` again with its most recent information. If
/// there is no existing state on the framework side, the call should /// there is no existing state on the framework side, the call should
/// fizzle. (This call is made without a client ID; indeed, without any /// fizzle.
/// arguments at all.)
/// ///
/// * `TextInputClient.onConnectionClosed`: The text input connection closed /// * `TextInputClient.onConnectionClosed`: The text input connection closed
/// on the platform side. For example the application is moved to /// on the platform side. For example the application is moved to
......
...@@ -1327,11 +1327,9 @@ class TextInput { ...@@ -1327,11 +1327,9 @@ class TextInput {
final List<dynamic> args = methodCall.arguments as List<dynamic>; final List<dynamic> args = methodCall.arguments as List<dynamic>;
// The updateEditingStateWithTag request (autofill) can come up even to a
// text field that doesn't have a connection.
if (method == 'TextInputClient.updateEditingStateWithTag') { if (method == 'TextInputClient.updateEditingStateWithTag') {
assert(_currentConnection!._client != null);
final TextInputClient client = _currentConnection!._client; final TextInputClient client = _currentConnection!._client;
assert(client != null);
final AutofillScope? scope = client.currentAutofillScope; final AutofillScope? scope = client.currentAutofillScope;
final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>; final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
for (final String tag in editingValue.keys) { for (final String tag in editingValue.keys) {
...@@ -1345,22 +1343,9 @@ class TextInput { ...@@ -1345,22 +1343,9 @@ class TextInput {
} }
final int client = args[0] as int; final int client = args[0] as int;
if (client != _currentConnection!._id) { // The incoming message was for a different client.
// If the client IDs don't match, the incoming message was for a different if (client != _currentConnection!._id)
// client.
bool debugAllowAnyway = false;
assert(() {
// In debug builds we allow "-1" as a magical client ID that ignores
// this verification step so that tests can always get through, even
// when they are not mocking the engine side of text input.
if (client == -1)
debugAllowAnyway = true;
return true;
}());
if (!debugAllowAnyway)
return; return;
}
switch (method) { switch (method) {
case 'TextInputClient.updateEditingState': case 'TextInputClient.updateEditingState':
_currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>)); _currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>));
......
...@@ -2111,7 +2111,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2111,7 +2111,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (_hasFocus) { if (_hasFocus) {
_openInputConnection(); _openInputConnection();
} else { } else {
widget.focusNode.requestFocus(); // This eventually calls _openInputConnection also, see _handleFocusChanged. widget.focusNode.requestFocus();
} }
} }
......
...@@ -195,15 +195,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -195,15 +195,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// Called by the test framework at the beginning of a widget test to /// Called by the test framework at the beginning of a widget test to
/// prepare the binding for the next test. /// 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() { void reset() {
_restorationManager = null; _restorationManager = null;
resetGestureBinding(); resetGestureBinding();
testTextInput.reset();
if (registerTestTextInput)
_testTextInput.register();
} }
@override @override
...@@ -243,8 +237,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -243,8 +237,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected @protected
bool get overrideHttpClient => true; bool get overrideHttpClient => true;
/// Determines whether the binding automatically registers [testTextInput] as /// Determines whether the binding automatically registers [testTextInput].
/// a fake keyboard implementation.
/// ///
/// Unit tests make use of this to mock out text input communication for /// 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 /// widgets. An integration test would set this to false, to test real IME
...@@ -252,19 +245,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -252,19 +245,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// ///
/// [TestTextInput.isRegistered] reports whether the text input mock is /// [TestTextInput.isRegistered] reports whether the text input mock is
/// registered or not. /// 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 @protected
bool get registerTestTextInput => true; bool get registerTestTextInput => true;
...@@ -339,6 +319,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -339,6 +319,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
binding.setupHttpOverrides(); binding.setupHttpOverrides();
} }
_testTextInput = TestTextInput(onCleared: _resetFocusedEditable); _testTextInput = TestTextInput(onCleared: _resetFocusedEditable);
if (registerTestTextInput) {
_testTextInput.register();
}
} }
@override @override
...@@ -532,20 +515,12 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -532,20 +515,12 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
TestTextInput get testTextInput => _testTextInput; TestTextInput get testTextInput => _testTextInput;
late TestTextInput _testTextInput; late TestTextInput _testTextInput;
/// The [State] of the current [EditableText] client of the onscreen keyboard. /// The current client of the onscreen keyboard. Callers must pump
/// /// an additional frame after setting this property to complete the
/// Setting this property to a new value causes the given [EditableTextState] /// focus change.
/// to focus itself and request the keyboard to establish a
/// [TextInputConnection].
///
/// Callers must pump an additional frame after setting this property to
/// complete the focus change.
/// ///
/// Instead of setting this directly, consider using /// Instead of setting this directly, consider using
/// [WidgetTester.showKeyboard]. /// [WidgetTester.showKeyboard].
//
// TODO(ianh): We should just remove this property and move the call to
// requestKeyboard to the WidgetTester.showKeyboard method.
EditableTextState? get focusedEditable => _focusedEditable; EditableTextState? get focusedEditable => _focusedEditable;
EditableTextState? _focusedEditable; EditableTextState? _focusedEditable;
set focusedEditable(EditableTextState? value) { set focusedEditable(EditableTextState? value) {
...@@ -824,8 +799,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -824,8 +799,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// alone so that we don't cause more spurious errors. // alone so that we don't cause more spurious errors.
runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets. runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets.
await pump(); await pump();
if (registerTestTextInput)
_testTextInput.unregister();
invariantTester(); invariantTester();
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser); _verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest); _verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
......
...@@ -15,18 +15,6 @@ export 'package:flutter/services.dart' show TextEditingValue, TextInputAction; ...@@ -15,18 +15,6 @@ export 'package:flutter/services.dart' show TextEditingValue, TextInputAction;
/// ///
/// Typical app tests will not need to use this class directly. /// 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 [enterText], [updateEditingValue], [receiveAction], and
/// [closeConnection] methods can be used even when the [TestTextInput] is not
/// registered. All other methods will assert if [isRegistered] is false.
///
/// See also: /// See also:
/// ///
/// * [WidgetTester.enterText], which uses this class to simulate keyboard input. /// * [WidgetTester.enterText], which uses this class to simulate keyboard input.
...@@ -49,76 +37,58 @@ class TestTextInput { ...@@ -49,76 +37,58 @@ class TestTextInput {
/// The messenger which sends the bytes for this channel, not null. /// The messenger which sends the bytes for this channel, not null.
BinaryMessenger get _binaryMessenger => ServicesBinding.instance!.defaultBinaryMessenger; BinaryMessenger get _binaryMessenger => ServicesBinding.instance!.defaultBinaryMessenger;
/// Log for method calls. /// Resets any internal state of this object and calls [register].
/// ///
/// For all registered channels, handled calls are added to the list. Can /// This method is invoked by the testing framework between tests. It should
/// be cleaned using `log.clear()`. /// not ordinarily be called by tests directly.
final List<MethodCall> log = <MethodCall>[]; void resetAndRegister() {
log.clear();
editingState = null;
setClientArgs = null;
_client = 0;
_isVisible = false;
register();
}
/// Installs this object as a mock handler for [SystemChannels.textInput]. /// 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); void register() => SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
/// Removes this object as a mock handler for [SystemChannels.textInput]. /// Removes this object as a mock handler for [SystemChannels.textInput].
/// ///
/// After calling this method, the channel will exchange messages with the /// After calling this method, the channel will exchange messages with the
/// Flutter engine instead of the stub. /// Flutter engine. Use this with [FlutterDriver] tests that need to display
/// /// on-screen keyboard provided by the operating system.
/// Called by the binding at the end of a (successful) test when
/// [TestWidgetsFlutterBinding.registerTestTextInput] is true.
void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null); void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null);
/// 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>[];
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput]. /// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
/// ///
/// The binding uses the [register] and [unregister] methods to control this /// Use [register] and [unregister] methods to control this value.
/// value when [TestWidgetsFlutterBinding.registerTestTextInput] is true.
bool get isRegistered => SystemChannels.textInput.checkMockMethodCallHandler(_handleTextInputCall); bool get isRegistered => SystemChannels.textInput.checkMockMethodCallHandler(_handleTextInputCall);
int? _client;
/// Whether there are any active clients listening to text input. /// Whether there are any active clients listening to text input.
bool get hasAnyClients { bool get hasAnyClients {
assert(isRegistered); assert(isRegistered);
return _client != null && _client! > 0; return _client > 0;
} }
/// The last set of arguments supplied to the `TextInput.setClient` and int _client = 0;
/// `TextInput.updateConfig` methods of this stub implementation.
/// Arguments supplied to the TextInput.setClient method call.
Map<String, dynamic>? setClientArgs; Map<String, dynamic>? setClientArgs;
/// The last set of arguments that [TextInputConnection.setEditingState] sent /// The last set of arguments that [TextInputConnection.setEditingState] sent
/// to this stub implementation (i.e. the arguments set to /// to the embedder.
/// `TextInput.setEditingState`).
/// ///
/// This is a map representation of a [TextEditingValue] object. For example, /// This is a map representation of a [TextEditingValue] object. For example,
/// it will have a `text` entry whose value matches the most recent /// it will have a `text` entry whose value matches the most recent
/// [TextEditingValue.text] that was sent to the embedder. /// [TextEditingValue.text] that was sent to the embedder.
Map<String, dynamic>? editingState; Map<String, dynamic>? editingState;
/// 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;
/// 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 reset() {
log.clear();
_client = null;
setClientArgs = null;
editingState = null;
_isVisible = false;
}
Future<dynamic> _handleTextInputCall(MethodCall methodCall) async { Future<dynamic> _handleTextInputCall(MethodCall methodCall) async {
log.add(methodCall); log.add(methodCall);
switch (methodCall.method) { switch (methodCall.method) {
...@@ -130,7 +100,7 @@ class TestTextInput { ...@@ -130,7 +100,7 @@ class TestTextInput {
setClientArgs = methodCall.arguments as Map<String, dynamic>; setClientArgs = methodCall.arguments as Map<String, dynamic>;
break; break;
case 'TextInput.clearClient': case 'TextInput.clearClient':
_client = null; _client = 0;
_isVisible = false; _isVisible = false;
onCleared?.call(); onCleared?.call();
break; break;
...@@ -146,69 +116,87 @@ class TestTextInput { ...@@ -146,69 +116,87 @@ class TestTextInput {
} }
} }
/// Simulates the user hiding the onscreen keyboard. /// Whether the onscreen keyboard is visible to the user.
/// bool get isVisible {
/// This does nothing but set the internal flag.
void hide() {
assert(isRegistered); assert(isRegistered);
_isVisible = false; return _isVisible;
}
/// Simulates the user typing the given text.
///
/// Calling this method replaces the content of the connected input field with
/// `text`, and places the caret at the end of the text.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject text when there is a real IME connection, for
/// example when using the [integration_test] library, there is a risk that
/// the real IME will become confused as to the current state of input.
void enterText(String text) {
updateEditingValue(TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
));
} }
bool _isVisible = false;
/// Simulates the user changing the [TextEditingValue] to the given value. /// Simulates the user changing the [TextEditingValue] to the given value.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject text when there is a real IME connection, for
/// example when using the [integration_test] library, there is a risk that
/// the real IME will become confused as to the current state of input.
void updateEditingValue(TextEditingValue value) { 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)
throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.');
_binaryMessenger.handlePlatformMessage( _binaryMessenger.handlePlatformMessage(
SystemChannels.textInput.name, SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall( SystemChannels.textInput.codec.encodeMethodCall(
MethodCall( MethodCall(
'TextInputClient.updateEditingState', 'TextInputClient.updateEditingState',
<dynamic>[_client ?? -1, value.toJSON()], <dynamic>[_client, value.toJSON()],
),
),
(ByteData? data) { /* response from framework is discarded */ },
);
}
/// 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() {
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)
throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.');
_binaryMessenger.handlePlatformMessage(
SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
MethodCall(
'TextInputClient.onConnectionClosed',
<dynamic>[_client,]
), ),
), ),
(ByteData? data) { /* response from framework is discarded */ }, (ByteData? data) { /* response from framework is discarded */ },
); );
} }
/// Simulates the user typing the given text.
///
/// Calling this method replaces the content of the connected input field with
/// `text`, and places the caret at the end of the text.
void enterText(String text) {
assert(isRegistered);
updateEditingValue(TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
));
}
/// Simulates the user pressing one of the [TextInputAction] buttons. /// Simulates the user pressing one of the [TextInputAction] buttons.
/// Does not check that the [TextInputAction] performed is an acceptable one /// Does not check that the [TextInputAction] performed is an acceptable one
/// based on the `inputAction` [setClientArgs]. /// based on the `inputAction` [setClientArgs].
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject an action when there is a real IME connection,
/// for example when using the [integration_test] library, there is a risk
/// that the real IME will become confused as to the current state of input.
Future<void> receiveAction(TextInputAction action) async { Future<void> receiveAction(TextInputAction action) async {
assert(isRegistered);
return TestAsyncUtils.guard(() { 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.
if (_client == 0) {
throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.');
}
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
_binaryMessenger.handlePlatformMessage( _binaryMessenger.handlePlatformMessage(
SystemChannels.textInput.name, SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall( SystemChannels.textInput.codec.encodeMethodCall(
MethodCall( MethodCall(
'TextInputClient.performAction', 'TextInputClient.performAction',
<dynamic>[_client ?? -1, action.toString()], <dynamic>[_client, action.toString()],
), ),
), ),
(ByteData? data) { (ByteData? data) {
...@@ -232,28 +220,9 @@ class TestTextInput { ...@@ -232,28 +220,9 @@ class TestTextInput {
}); });
} }
/// Simulates the user closing the text input connection. /// Simulates the user hiding the onscreen keyboard.
/// void hide() {
/// For example: assert(isRegistered);
/// _isVisible = false;
/// * User pressed the home button and sent the application to background.
/// * User closed the virtual keyboard.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject text when there is a real IME connection, for
/// example when using the [integration_test] library, there is a risk that
/// the real IME will become confused as to the current state of input.
void closeConnection() {
_binaryMessenger.handlePlatformMessage(
SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
MethodCall(
'TextInputClient.onConnectionClosed',
<dynamic>[_client ?? -1],
),
),
(ByteData? data) { /* response from framework is discarded */ },
);
} }
} }
...@@ -149,6 +149,7 @@ void testWidgets( ...@@ -149,6 +149,7 @@ void testWidgets(
() async { () async {
binding.reset(); binding.reset();
debugResetSemanticsIdCounter(); debugResetSemanticsIdCounter();
tester.resetTestTextInput();
Object? memento; Object? memento;
try { try {
memento = await variant.setUp(value); memento = await variant.setUp(value);
...@@ -1001,14 +1002,19 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -1001,14 +1002,19 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// ///
/// Typical app tests will not need to use this value. To add text to widgets /// Typical app tests will not need to use this value. To add text to widgets
/// like [TextField] or [TextFormField], call [enterText]. /// like [TextField] or [TextFormField], call [enterText].
///
/// 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; 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();
}
/// Give the text input widget specified by [finder] the focus, as if the /// Give the text input widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared. /// onscreen keyboard had appeared.
/// ///
...@@ -1029,9 +1035,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -1029,9 +1035,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
matchRoot: true, matchRoot: true,
), ),
); );
// Setting focusedEditable causes the binding to call requestKeyboard()
// on the EditableTextState, which itself eventually calls TextInput.attach
// to establish the connection.
binding.focusedEditable = editable; binding.focusedEditable = editable;
await pump(); await pump();
}); });
...@@ -1049,12 +1052,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -1049,12 +1052,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// ///
/// To just give [finder] the focus without entering any text, /// To just give [finder] the focus without entering any text,
/// see [showKeyboard]. /// see [showKeyboard].
///
/// To enter text into other widgets (e.g. a custom widget that maintains a
/// TextInputConnection the way that a [EditableText] does), first ensure that
/// that widget has an open connection (e.g. by using [tap] to to focus it),
/// then call `testTextInput.enterText` directly (see
/// [TestTextInput.enterText]).
Future<void> enterText(Finder finder, String text) async { Future<void> enterText(Finder finder, String text) async {
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
await showKeyboard(finder); await showKeyboard(finder);
......
...@@ -11,8 +11,6 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -11,8 +11,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:test_api/test_api.dart' as test_package; import 'package:test_api/test_api.dart' as test_package;
void main() { void main() {
final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
group(TestViewConfiguration, () { group(TestViewConfiguration, () {
test('is initialized with top-level window if one is not provided', () { test('is initialized with top-level window if one is not provided', () {
// The code below will throw without the default. // The code below will throw without the default.
...@@ -22,32 +20,15 @@ void main() { ...@@ -22,32 +20,15 @@ void main() {
group(AutomatedTestWidgetsFlutterBinding, () { group(AutomatedTestWidgetsFlutterBinding, () {
test('allows setting defaultTestTimeout to 5 minutes', () { test('allows setting defaultTestTimeout to 5 minutes', () {
final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding();
binding.defaultTestTimeout = const test_package.Timeout(Duration(minutes: 5)); binding.defaultTestTimeout = const test_package.Timeout(Duration(minutes: 5));
expect(binding.defaultTestTimeout.duration, const 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 { test('Initializes httpOverrides and testTextInput', () async {
assert(order == 0); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
expect(binding.testTextInput, isNotNull); expect(binding.testTextInput.isRegistered, true);
expect(binding.testTextInput.isRegistered, isFalse);
expect(HttpOverrides.current, isNotNull); 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