This reverts commit 285b475124216c79198ff2d92e673d1dd10280a0.
This commit is contained in:
parent
5b0eea8a26
commit
cbdd9c4787
@ -20,6 +20,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);
|
||||||
@ -45,6 +48,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);
|
||||||
@ -70,6 +76,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'));
|
||||||
@ -103,6 +112,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);
|
||||||
@ -135,6 +147,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);
|
||||||
@ -182,6 +197,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 {
|
|||||||
/// 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 {
|
|||||||
/// 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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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>));
|
||||||
|
@ -2112,7 +2112,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
|
|||||||
|
|
||||||
/// 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
|
|||||||
@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
|
|||||||
///
|
///
|
||||||
/// [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
|
|||||||
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
|
|||||||
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
|
|||||||
// 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);
|
||||||
|
@ -14,18 +14,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.
|
||||||
@ -48,76 +36,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;
|
||||||
|
|
||||||
|
/// Resets any internal state of this object and calls [register].
|
||||||
|
///
|
||||||
|
/// This method is invoked by the testing framework between tests. It should
|
||||||
|
/// not ordinarily be called by tests directly.
|
||||||
|
void resetAndRegister() {
|
||||||
|
log.clear();
|
||||||
|
editingState = null;
|
||||||
|
setClientArgs = null;
|
||||||
|
_client = 0;
|
||||||
|
_isVisible = false;
|
||||||
|
register();
|
||||||
|
}
|
||||||
|
/// Installs this object as a mock handler for [SystemChannels.textInput].
|
||||||
|
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.
|
/// Log for method calls.
|
||||||
///
|
///
|
||||||
/// For all registered channels, handled calls are added to the list. Can
|
/// For all registered channels, handled calls are added to the list. Can
|
||||||
/// be cleaned using `log.clear()`.
|
/// be cleaned using `log.clear()`.
|
||||||
final List<MethodCall> log = <MethodCall>[];
|
final List<MethodCall> log = <MethodCall>[];
|
||||||
|
|
||||||
/// 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 instead of the stub.
|
|
||||||
///
|
|
||||||
/// 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].
|
/// 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) {
|
||||||
@ -129,7 +99,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;
|
||||||
@ -145,69 +115,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 */ },
|
(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 */ },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
@ -231,28 +219,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(
|
|||||||
() 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
|
|||||||
///
|
///
|
||||||
/// 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
|
|||||||
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
|
|||||||
///
|
///
|
||||||
/// 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';
|
|||||||
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() {
|
|||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user