Commit 4ad8271b authored by Dan Field's avatar Dan Field Committed by Flutter GitHub Bot

Reland text state (#47464)

parent bd25f70c
...@@ -2961,7 +2961,7 @@ void main() { ...@@ -2961,7 +2961,7 @@ void main() {
), ),
), ),
); );
expect(tester.testTextInput.editingState['text'], isEmpty); expect(tester.testTextInput.editingState, isNull);
// Initial state with null controller. // Initial state with null controller.
await tester.tap(find.byType(TextField)); await tester.tap(find.byType(TextField));
......
...@@ -3928,10 +3928,6 @@ void main() { ...@@ -3928,10 +3928,6 @@ void main() {
}); });
testWidgets('input imm channel calls are ordered correctly', (WidgetTester tester) async { testWidgets('input imm channel calls are ordered correctly', (WidgetTester tester) async {
final List<MethodCall> log = <MethodCall>[];
SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
const String testText = 'flutter is the best!'; const String testText = 'flutter is the best!';
final TextEditingController controller = TextEditingController(text: testText); final TextEditingController controller = TextEditingController(text: testText);
final EditableText et = EditableText( final EditableText et = EditableText(
...@@ -3956,14 +3952,107 @@ void main() { ...@@ -3956,14 +3952,107 @@ void main() {
)); ));
await tester.showKeyboard(find.byType(EditableText)); await tester.showKeyboard(find.byType(EditableText));
expect(log.length, 7);
// TextInput.show should be before TextInput.setEditingState // TextInput.show should be before TextInput.setEditingState
final List<String> logOrder = <String>['TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', 'TextInput.show']; final List<String> logOrder = <String>['TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', 'TextInput.show'];
expect(tester.testTextInput.log.length, 7);
int index = 0;
for (MethodCall m in tester.testTextInput.log) {
expect(m.method, logOrder[index]);
index++;
}
});
testWidgets('setEditingState is not called when text changes', (WidgetTester tester) async {
// We shouldn't get a message here because this change is owned by the platform side.
const String testText = 'flutter is the best!';
final TextEditingController controller = TextEditingController(text: testText);
final EditableText et = EditableText(
showSelectionHandles: true,
maxLines: 2,
controller: controller,
focusNode: FocusNode(),
cursorColor: Colors.red,
backgroundCursorColor: Colors.blue,
style: Typography(platform: TargetPlatform.android).black.subhead.copyWith(fontFamily: 'Roboto'),
keyboardType: TextInputType.text,
);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
child: et,
),
),
));
await tester.enterText(find.byType(EditableText), '...');
final List<String> logOrder = <String>[
'TextInput.setClient',
'TextInput.show',
'TextInput.setEditableSizeAndTransform',
'TextInput.setStyle',
'TextInput.setEditingState',
'TextInput.setEditingState',
'TextInput.show',
];
expect(tester.testTextInput.log.length, logOrder.length);
int index = 0;
for (MethodCall m in tester.testTextInput.log) {
expect(m.method, logOrder[index]);
index++;
}
expect(tester.testTextInput.editingState['text'], 'flutter is the best!');
});
testWidgets('setEditingState is called when text changes on controller', (WidgetTester tester) async {
// We should get a message here because this change is owned by the framework side.
const String testText = 'flutter is the best!';
final TextEditingController controller = TextEditingController(text: testText);
final EditableText et = EditableText(
showSelectionHandles: true,
maxLines: 2,
controller: controller,
focusNode: FocusNode(),
cursorColor: Colors.red,
backgroundCursorColor: Colors.blue,
style: Typography(platform: TargetPlatform.android).black.subhead.copyWith(fontFamily: 'Roboto'),
keyboardType: TextInputType.text,
);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
child: et,
),
),
));
await tester.showKeyboard(find.byType(EditableText));
controller.text += '...';
await tester.idle();
final List<String> logOrder = <String>[
'TextInput.setClient',
'TextInput.show',
'TextInput.setEditableSizeAndTransform',
'TextInput.setStyle',
'TextInput.setEditingState',
'TextInput.setEditingState',
'TextInput.show',
'TextInput.setEditingState',
];
expect(tester.testTextInput.log.length, logOrder.length);
int index = 0; int index = 0;
for (MethodCall m in log) { for (MethodCall m in tester.testTextInput.log) {
expect(m.method, logOrder[index]); expect(m.method, logOrder[index]);
index++; index++;
} }
expect(tester.testTextInput.editingState['text'], 'flutter is the best!...');
}); });
} }
......
...@@ -39,6 +39,18 @@ class TestTextInput { ...@@ -39,6 +39,18 @@ 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]. /// Installs this object as a mock handler for [SystemChannels.textInput].
void register() { void register() {
SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall); SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
...@@ -64,6 +76,7 @@ class TestTextInput { ...@@ -64,6 +76,7 @@ class TestTextInput {
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput]. /// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
/// ///
/// Use [register] and [unregister] methods to control this value. /// Use [register] and [unregister] methods to control this value.
// TODO(dnfield): This is unreliable. https://github.com/flutter/flutter/issues/47180
bool get isRegistered => _isRegistered; bool get isRegistered => _isRegistered;
bool _isRegistered = false; bool _isRegistered = false;
......
...@@ -121,6 +121,7 @@ void testWidgets( ...@@ -121,6 +121,7 @@ void testWidgets(
return binding.runTest( return binding.runTest(
() async { () async {
debugResetSemanticsIdCounter(); debugResetSemanticsIdCounter();
tester.resetTestTextInput();
await callback(tester); await callback(tester);
semanticsHandle?.dispose(); semanticsHandle?.dispose();
}, },
...@@ -692,6 +693,17 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -692,6 +693,17 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// like [TextField] or [TextFormField], call [enterText]. /// like [TextField] or [TextFormField], call [enterText].
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.
/// ///
......
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