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 {