Unverified Commit 7957c569 authored by Dan Field's avatar Dan Field Committed by GitHub

Respond to TextInputClient.reattach messages. (#43959)

parent b94c1a41
......@@ -159,6 +159,13 @@ class SystemChannels {
/// second argument is a [String] consisting of the stringification of one
/// of the values of the [TextInputAction] enum.
///
/// * `TextInputClient.requestExistingInputState`: The embedding may have
/// lost its internal state about the current editing client, if there is
/// one. The framework should call `TextInput.setClient` and
/// `TextInput.setEditingState` again with its most recent information. If
/// there is no existing state on the framework side, the call should
/// fizzle.
///
/// * `TextInputClient.onConnectionClosed`: The text input connection closed
/// on the platform side. For example the application is moved to
/// background or used closed the virtual keyboard. This method informs
......
......@@ -1395,19 +1395,20 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (!_hasInputConnection) {
final TextEditingValue localValue = _value;
_lastKnownRemoteTextEditingValue = localValue;
_textInputConnection = TextInput.attach(this,
TextInputConfiguration(
inputType: widget.keyboardType,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
enableSuggestions: widget.enableSuggestions,
inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline
? TextInputAction.newline
: TextInputAction.done
),
textCapitalization: widget.textCapitalization,
keyboardAppearance: widget.keyboardAppearance,
_textInputConnection = TextInput.attach(
this,
TextInputConfiguration(
inputType: widget.keyboardType,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
enableSuggestions: widget.enableSuggestions,
inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline
? TextInputAction.newline
: TextInputAction.done
),
textCapitalization: widget.textCapitalization,
keyboardAppearance: widget.keyboardAppearance,
),
);
_textInputConnection.show();
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show utf8;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding;
import '../flutter_test_alternative.dart';
......@@ -9,6 +11,66 @@ import '../flutter_test_alternative.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('TextInput message channels', () {
FakeTextChannel fakeTextChannel;
FakeTextInputClient client;
setUp(() {
fakeTextChannel = FakeTextChannel((MethodCall call) async {});
TextInput.setChannel(fakeTextChannel);
client = FakeTextInputClient();
});
tearDown(() {
TextInputConnection.debugResetId();
TextInput.setChannel(SystemChannels.textInput);
});
test('text input client handler responds to reattach with setClient', () async {
TextInput.attach(client, client.configuration);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
]);
fakeTextChannel.incoming(const MethodCall('TextInputClient.requestExistingInputState', null));
expect(fakeTextChannel.outgoingCalls.length, 2);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// From original attach
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
// From requestExistingInputState
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
]);
});
test('text input client handler responds to reattach with setClient and text state', () async {
final TextInputConnection connection = TextInput.attach(client, client.configuration);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
]);
const TextEditingValue editingState = TextEditingValue(text: 'foo');
connection.setEditingState(editingState);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
MethodCall('TextInput.setEditingState', editingState.toJSON()),
]);
fakeTextChannel.incoming(const MethodCall('TextInputClient.requestExistingInputState', null));
expect(fakeTextChannel.outgoingCalls.length, 4);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// attach
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
// set editing state 1
MethodCall('TextInput.setEditingState', editingState.toJSON()),
// both from requestExistingInputState
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
MethodCall('TextInput.setEditingState', editingState.toJSON()),
]);
});
});
group('TextInputConfiguration', () {
test('sets expected defaults', () {
const TextInputConfiguration configuration = TextInputConfiguration();
......@@ -113,7 +175,7 @@ void main() {
});
}
class FakeTextInputClient extends TextInputClient {
class FakeTextInputClient implements TextInputClient {
String latestMethodCall = '';
@override
......@@ -135,4 +197,68 @@ class FakeTextInputClient extends TextInputClient {
void connectionClosed() {
latestMethodCall = 'connectionClosed';
}
TextInputConfiguration get configuration => const TextInputConfiguration();
}
class FakeTextChannel implements MethodChannel {
FakeTextChannel(this.outgoing) : assert(outgoing != null);
Future<void> Function(MethodCall) outgoing;
Future<void> Function(MethodCall) incoming;
List<MethodCall> outgoingCalls = <MethodCall>[];
@override
BinaryMessenger get binaryMessenger => throw UnimplementedError();
@override
MethodCodec get codec => const JSONMethodCodec();
@override
Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
@override
Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
@override
Future<T> invokeMethod<T>(String method, [dynamic arguments]) {
final MethodCall call = MethodCall(method, arguments);
outgoingCalls.add(call);
return outgoing(call);
}
@override
String get name => 'flutter/textinput';
@override
void setMethodCallHandler(Future<void> Function(MethodCall call) handler) {
incoming = handler;
}
@override
void setMockMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
void validateOutgoingMethodCalls(List<MethodCall> calls) {
expect(outgoingCalls.length, calls.length);
bool hasError = false;
for (int i = 0; i < calls.length; i++) {
final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]);
final ByteData expectedData = codec.encodeMethodCall(calls[i]);
final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List());
final String expectedString = utf8.decode(expectedData.buffer.asUint8List());
if (outgoingString != expectedString) {
print(
'Index $i did not match:\n'
' actual: ${outgoingCalls[i]}'
' expected: ${calls[i]}');
hasError = true;
}
}
if (hasError) {
fail('Calls did not match.');
}
}
}
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