Commit 80a8c562 authored by Hans Muller's avatar Hans Muller Committed by GitHub

WidgetTester enterText() and showKeyboard() can specify an EditableText ancestor (#9398)

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