diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 5cb86fb4f56..c9a9a28f20f 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -103,7 +103,7 @@ void main() { Future checkText(String testValue) async { return TestAsyncUtils.guard(() async { - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); // Check that the onChanged event handler fired. expect(textFieldValue, equals(testValue)); @@ -137,7 +137,7 @@ void main() { } await tester.pumpWidget(builder()); - await tester.showKeyboard(find.byType(EditableText)); + await tester.showKeyboard(find.byType(TextField)); final EditableTextState editableText = tester.state(find.byType(EditableText)); @@ -157,7 +157,7 @@ void main() { } await checkCursorToggle(); - await tester.showKeyboard(find.byType(EditableText)); + await tester.showKeyboard(find.byType(TextField)); // Try the test again with a nonempty EditableText. tester.testTextInput.updateEditingValue(const TextEditingValue( @@ -182,7 +182,7 @@ void main() { } await tester.pumpWidget(builder()); - await tester.showKeyboard(find.byType(EditableText)); + await tester.showKeyboard(find.byType(TextField)); const String testValue = 'ABC'; tester.testTextInput.updateEditingValue(const TextEditingValue( @@ -211,7 +211,7 @@ void main() { expect(controller.selection.extentOffset, -1); final String testValue = 'abc def ghi'; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); await tester.pumpWidget(builder()); @@ -249,7 +249,7 @@ void main() { await tester.pumpWidget(builder()); final String testValue = 'abc def ghi'; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); expect(controller.value.text, testValue); await tester.pumpWidget(builder()); @@ -284,7 +284,7 @@ void main() { await tester.pumpWidget(builder()); final String testValue = 'abc def ghi'; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); await tester.pumpWidget(builder()); @@ -346,7 +346,7 @@ void main() { await tester.pumpWidget(builder()); final String testValue = 'abc def ghi'; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); await tester.pumpWidget(builder()); // Tap the selection handle to bring up the "paste / select all" menu. @@ -398,7 +398,7 @@ void main() { await tester.pumpWidget(builder()); final String testValue = 'abc def ghi'; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); await tester.pumpWidget(builder()); // Tap the selection handle to bring up the "paste / select all" menu. @@ -449,12 +449,12 @@ void main() { final RenderBox inputBox = findInputBox(); final Size emptyInputSize = inputBox.size; - await tester.enterText(find.byType(EditableText), 'No wrapping here.'); + await tester.enterText(find.byType(TextField), 'No wrapping here.'); await tester.pumpWidget(builder(3)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, equals(emptyInputSize)); - await tester.enterText(find.byType(EditableText), kThreeLines); + await tester.enterText(find.byType(TextField), kThreeLines); await tester.pumpWidget(builder(3)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, greaterThan(emptyInputSize)); @@ -462,13 +462,13 @@ void main() { final Size threeLineInputSize = inputBox.size; // An extra line won't increase the size because we max at 3. - await tester.enterText(find.byType(EditableText), kFourLines); + await tester.enterText(find.byType(TextField), kFourLines); await tester.pumpWidget(builder(3)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, threeLineInputSize); // But now it will. - await tester.enterText(find.byType(EditableText), kFourLines); + await tester.enterText(find.byType(TextField), kFourLines); await tester.pumpWidget(builder(4)); expect(findInputBox(), equals(inputBox)); expect(inputBox.size, greaterThan(threeLineInputSize)); @@ -493,7 +493,7 @@ void main() { final String testValue = kThreeLines; final String cutValue = 'First line of stuff '; - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); await tester.pumpWidget(builder()); @@ -572,7 +572,7 @@ void main() { await tester.pumpWidget(builder()); await tester.pump(const Duration(seconds: 1)); - await tester.enterText(find.byType(EditableText), kFourLines); + await tester.enterText(find.byType(TextField), kFourLines); await tester.pumpWidget(builder()); await tester.pump(const Duration(seconds: 1)); @@ -660,7 +660,7 @@ void main() { Future checkText(String testValue) { return TestAsyncUtils.guard(() async { - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); // Check that the onChanged event handler fired. expect(textFieldValue, equals(testValue)); @@ -694,7 +694,7 @@ void main() { Future checkText(String testValue) async { return TestAsyncUtils.guard(() async { - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); // Check that the onChanged event handler fired. expect(textFieldValue, equals(testValue)); @@ -866,7 +866,7 @@ void main() { expect(topLeft.dx, equals(399.0)); - await tester.enterText(find.byType(EditableText), 'abcd'); + await tester.enterText(find.byType(TextField), 'abcd'); await tester.pump(); topLeft = editable.localToGlobal( @@ -900,7 +900,7 @@ void main() { expect(topLeft.dx, equals(399.0)); - await tester.enterText(find.byType(EditableText), 'abcd'); + await tester.enterText(find.byType(TextField), 'abcd'); await tester.pump(); topLeft = editable.localToGlobal( diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index 09aebc5f347..4b7cde472aa 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -28,7 +28,7 @@ void main() { expect(fieldValue, isNull); Future checkText(String testValue) async { - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextFormField), testValue); formKey.currentState.save(); // pump'ing is unnecessary because callback happens regardless of frames expect(fieldValue, equals(testValue)); @@ -58,7 +58,7 @@ void main() { expect(fieldValue, isNull); Future checkText(String testValue) async { - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextField), testValue); // pump'ing is unnecessary because callback happens regardless of frames expect(fieldValue, equals(testValue)); } @@ -90,7 +90,7 @@ void main() { Future checkErrorText(String testValue) async { formKey.currentState.reset(); - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextFormField), testValue); await tester.pumpWidget(builder(false)); // We have to manually validate if we're not autovalidating. @@ -101,7 +101,7 @@ void main() { // Try again with autovalidation. Should validate immediately. formKey.currentState.reset(); - await tester.enterText(find.byType(EditableText), testValue); + await tester.enterText(find.byType(TextFormField), testValue); await tester.pumpWidget(builder(true)); expect(find.text(errorText(testValue)), findsOneWidget); @@ -141,7 +141,7 @@ void main() { await tester.pumpWidget(builder()); Future checkErrorText(String testValue) async { - await tester.enterText(find.byType(EditableText).first, testValue); + await tester.enterText(find.byType(TextFormField).first, testValue); await tester.pump(); // Check for a new Text widget with our error text. @@ -172,7 +172,7 @@ void main() { } await tester.pumpWidget(builder()); - await tester.showKeyboard(find.byType(EditableText)); + await tester.showKeyboard(find.byType(TextFormField)); // initial value should be loaded into keyboard editing state expect(tester.testTextInput.editingState, isNotNull); @@ -184,7 +184,7 @@ void main() { // sanity check, make sure we can still edit the text and everything updates expect(inputKey.currentState.value, equals(initialValue)); - await tester.enterText(find.byType(EditableText), 'world'); + await tester.enterText(find.byType(TextFormField), 'world'); await tester.pump(); expect(inputKey.currentState.value, equals('world')); expect(editableText.widget.controller.text, equals('world')); @@ -214,7 +214,7 @@ void main() { expect(fieldValue, isNull); expect(formKey.currentState.validate(), isTrue); - await tester.enterText(find.byType(EditableText), 'Test'); + await tester.enterText(find.byType(TextFormField), 'Test'); await tester.pumpWidget(builder(false)); // Form wasn't saved yet. diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index af8b7cb4621..cc9ef62f37c 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -189,10 +189,13 @@ class CommonFinders { /// of: find.widgetWithText(Row, 'label_1'), matching: find.text('value_1') /// ), findsOneWidget); /// + /// If the [matchRoot] argument is true then the widget(s) specified by [of] + /// will be matched along with the descendants. + /// /// If the [skipOffstage] argument is true (the default), then nodes that are /// [Offstage] or that are from inactive [Route]s are skipped. - Finder descendant({ Finder of, Finder matching, bool skipOffstage: true }) { - return new _DescendantFinder(of, matching, skipOffstage: skipOffstage); + Finder descendant({ Finder of, Finder matching, bool matchRoot: false, bool skipOffstage: true }) { + return new _DescendantFinder(of, matching, matchRoot: matchRoot, skipOffstage: skipOffstage); } } @@ -488,13 +491,21 @@ class _ElementPredicateFinder extends MatchFinder { } class _DescendantFinder extends Finder { - _DescendantFinder(this.ancestor, this.descendant, { bool skipOffstage: true }) : super(skipOffstage: skipOffstage); + _DescendantFinder(this.ancestor, this.descendant, { + this.matchRoot: false, + bool skipOffstage: true, + }) : super(skipOffstage: skipOffstage); final Finder ancestor; final Finder descendant; + final bool matchRoot; @override - String get description => '${descendant.description} that has ancestor(s) with ${ancestor.description} '; + String get description { + if (matchRoot) + return '${descendant.description} in the subtree(s) beginning with ${ancestor.description}'; + return '${descendant.description} that has ancestor(s) with ${ancestor.description}'; + } @override Iterable apply(Iterable candidates) { @@ -503,8 +514,12 @@ class _DescendantFinder extends Finder { @override Iterable get allCandidates { - return ancestor.evaluate().expand( + final Iterable ancestorElements = ancestor.evaluate(); + final List candidates = ancestorElements.expand( (Element element) => collectAllElementsFrom(element, skipOffstage: skipOffstage) ).toSet().toList(); + if (matchRoot) + candidates.insertAll(0, ancestorElements); + return candidates; } } diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index b4b43b7ea61..ac871a5da21 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -436,19 +436,25 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// Returns the TestTextInput singleton. /// /// Typical app tests will not need to use this value. To add text to widgets - /// like [Input] or [TextField], call [enterText]. + /// like [TextField] or [FormTextField], call [enterText]. TestTextInput get testTextInput => binding.testTextInput; - /// Give the EditableText 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. /// - /// Tests that just need to add text to widgets like [Input] or [TextField] - /// only need to call [enterText]. + /// The widget specified by [finder] must be an [EditableText] or have + /// an [EditableText] descendant. For example `find.byType(TextField)` + /// or `find.byType(FormTextField)`, or `find.byType(EditableText)`. + /// + /// Tests that just need to add text to widgets like [TextField] + /// or [FormTextField] only need to call [enterText]. Future showKeyboard(Finder finder) async { return TestAsyncUtils.guard(() async { - // TODO(hansmuller): Once find.descendant (#7789) lands replace the following - // RHS with state(find.descendant(finder), find.byType(EditableText)). - final EditableTextState editable = state(finder); + final EditableTextState editable = state(find.descendant( + of: finder, + matching: find.byType(EditableText), + matchRoot: true, + )); if (editable != binding.focusedEditable) { binding.focusedEditable = editable; await pump(); @@ -456,8 +462,15 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker }); } - /// Give the EditableText widget specified by [finder] the focus and + /// Give the text input widget specified by [finder] the focus and /// enter [text] as if it been provided by the onscreen keyboard. + /// + /// The widget specified by [finder] must be an [EditableText] or have + /// an [EditableText] descendant. For example `find.byType(TextField)` + /// or `find.byType(FormTextField)`, or `find.byType(EditableText)`. + /// + /// To just give [finder] the focus without entering any text, + /// see [showKeyboard]. Future enterText(Finder finder, String text) async { return TestAsyncUtils.guard(() async { await showKeyboard(finder); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index e3c061bf239..db5a57818a7 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -184,6 +184,30 @@ void main() { contains('Actual: ?:[ + new Column(children: [const Text('foo'), const Text('bar')]) + ])); + + expect(find.descendant( + of: find.widgetWithText(Row, 'foo'), + matching: find.byType(Row), + ), findsNothing); + }); + + testWidgets('Match the root', (WidgetTester tester) async { + await tester.pumpWidget(new Row(children: [ + new Column(children: [const Text('foo'), const Text('bar')]) + ])); + + expect(find.descendant( + of: find.widgetWithText(Row, 'foo'), + matching: find.byType(Row), + matchRoot: true, + ), findsOneWidget); + }); + }); testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {