Clear the keyboard state in the test framework when keyboard is closed. (#18615)
diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart
index 872455c..263c560 100644
--- a/packages/flutter/test/material/text_form_field_test.dart
+++ b/packages/flutter/test/material/text_form_field_test.dart
@@ -49,7 +49,7 @@
await tester.pump();
expect(_called, true);
});
-
+
testWidgets('autovalidate is passed to super', (WidgetTester tester) async {
int _validateCalled = 0;
diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart
index 4756b5c..526e1e1 100644
--- a/packages/flutter/test/widgets/form_test.dart
+++ b/packages/flutter/test/widgets/form_test.dart
@@ -99,8 +99,9 @@
Future<Null> checkErrorText(String testValue) async {
formKey.currentState.reset();
- await tester.enterText(find.byType(TextFormField), testValue);
await tester.pumpWidget(builder(false));
+ await tester.enterText(find.byType(TextFormField), testValue);
+ await tester.pump();
// We have to manually validate if we're not autovalidating.
expect(find.text(errorText(testValue)), findsNothing);
@@ -110,8 +111,9 @@
// Try again with autovalidation. Should validate immediately.
formKey.currentState.reset();
- await tester.enterText(find.byType(TextFormField), testValue);
await tester.pumpWidget(builder(true));
+ await tester.enterText(find.byType(TextFormField), testValue);
+ await tester.pump();
expect(find.text(errorText(testValue)), findsOneWidget);
}
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 403ba11..a149ceb 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -152,7 +152,7 @@
void initInstances() {
timeDilation = 1.0; // just in case the developer has artificially changed it for development
HttpOverrides.global = new _MockHttpOverrides();
- _testTextInput = new TestTextInput()..register();
+ _testTextInput = new TestTextInput(onCleared: _resetFocusedEditable)..register();
super.initInstances();
}
@@ -280,10 +280,20 @@
/// The current client of the onscreen keyboard. Callers must pump
/// an additional frame after setting this property to complete the
/// the focus change.
+ ///
+ /// Instead of setting this directly, consider using
+ /// [WidgetTester.showKeyboard].
EditableTextState get focusedEditable => _focusedEditable;
EditableTextState _focusedEditable;
set focusedEditable(EditableTextState value) {
- _focusedEditable = value..requestKeyboard();
+ if (_focusedEditable != value) {
+ _focusedEditable = value;
+ value?.requestKeyboard();
+ }
+ }
+
+ void _resetFocusedEditable() {
+ _focusedEditable = null;
}
/// Returns the exception most recently caught by the Flutter framework.
diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart
index 436a1c8..e8bebca 100644
--- a/packages/flutter_test/lib/src/test_text_input.dart
+++ b/packages/flutter_test/lib/src/test_text_input.dart
@@ -6,9 +6,12 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
+import 'package:flutter/foundation.dart';
import 'widget_tester.dart';
+export 'package:flutter/services.dart' show TextEditingValue, TextInputAction;
+
/// A testing stub for the system's onscreen keyboard.
///
/// Typical app tests will not need to use this class directly.
@@ -19,6 +22,19 @@
/// * [WidgetTester.showKeyboard], which uses this class to simulate showing the
/// popup keyboard and initializing its text.
class TestTextInput {
+ /// Create a fake keyboard backend.
+ ///
+ /// The [onCleared] argument may be set to be notified of when the keyboard
+ /// is dismissed.
+ TestTextInput({ this.onCleared });
+
+ /// Called when the keyboard goes away.
+ ///
+ /// To use the methods on this API that send fake keyboard messages (such as
+ /// [updateEditingValue], [enterText], or [receiveAction]), the keyboard must
+ /// first be requested, e.g. using [WidgetTester.showKeyboard].
+ final VoidCallback onCleared;
+
/// Installs this object as a mock handler for [SystemChannels.textInput].
void register() {
SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
@@ -63,6 +79,8 @@
case 'TextInput.clearClient':
_client = 0;
_isVisible = false;
+ if (onCleared != null)
+ onCleared();
break;
case 'TextInput.setEditingState':
editingState = methodCall.arguments;
@@ -84,9 +102,8 @@
void updateEditingValue(TextEditingValue value) {
// 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 new TestFailure('_client must be non-zero');
- }
+ if (_client == 0)
+ throw new TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.');
BinaryMessages.handlePlatformMessage(
SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
@@ -112,9 +129,8 @@
void receiveAction(TextInputAction action) {
// 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 new TestFailure('_client must be non-zero');
- }
+ if (_client == 0)
+ throw new TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.');
BinaryMessages.handlePlatformMessage(
SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index f3fb46b..ae51e57 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -558,6 +558,8 @@
/// Give the text input widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared.
///
+ /// Implies a call to [pump].
+ ///
/// The widget specified by [finder] must be an [EditableText] or have
/// an [EditableText] descendant. For example `find.byType(TextField)`
/// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
@@ -566,15 +568,15 @@
/// or [TextFormField] only need to call [enterText].
Future<Null> showKeyboard(Finder finder) async {
return TestAsyncUtils.guard(() async {
- final EditableTextState editable = state(find.descendant(
- of: finder,
- matching: find.byType(EditableText),
- matchRoot: true,
- ));
- if (editable != binding.focusedEditable) {
- binding.focusedEditable = editable;
- await pump();
- }
+ final EditableTextState editable = state(
+ find.descendant(
+ of: finder,
+ matching: find.byType(EditableText),
+ matchRoot: true,
+ ),
+ );
+ binding.focusedEditable = editable;
+ await pump();
});
}
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index d261b84..441d59d 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -506,6 +506,27 @@
});
});
});
+
+ testWidgets('showKeyboard can be called twice', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ new MaterialApp(
+ home: new Material(
+ child: new Center(
+ child: new TextFormField(),
+ ),
+ ),
+ ),
+ );
+ await tester.showKeyboard(find.byType(TextField));
+ tester.testTextInput.receiveAction(TextInputAction.done);
+ await tester.pump();
+ await tester.showKeyboard(find.byType(TextField));
+ tester.testTextInput.receiveAction(TextInputAction.done);
+ await tester.pump();
+ await tester.showKeyboard(find.byType(TextField));
+ await tester.showKeyboard(find.byType(TextField));
+ await tester.pump();
+ });
}
class FakeMatcher extends AsyncMatcher {