Minor widget_tester refactoring and docs (#3472)
This reorders some classes so that this file makes more sense, and adds
a bunch of docs. It also makes the following changes:
* Move allElements from Instrumentation to TestWidgets. (Instrumentation
is going away.)
* Remove findElements.
* Rename byElement to byElementPredicate
* Rename byPredicate to byWidgetPredicate
* Implement _WidgetPredicateFinder so that byWidgetPredicate has good
messages
* Fix one use of byElementPredicate to use byWidgetPredicate.
diff --git a/dev/manual_tests/test/card_collection_test.dart b/dev/manual_tests/test/card_collection_test.dart
index 377ce1c..ec7760b 100644
--- a/dev/manual_tests/test/card_collection_test.dart
+++ b/dev/manual_tests/test/card_collection_test.dart
@@ -15,8 +15,7 @@
tester.pump(); // see https://github.com/flutter/flutter/issues/1865
tester.pump(); // triggers a frame
- Finder navigationMenu = find.byElement((Element element) {
- Widget widget = element.widget;
+ Finder navigationMenu = find.byWidgetPredicate((Widget widget) {
if (widget is Tooltip)
return widget.message == 'Open navigation menu';
return false;
diff --git a/examples/material_gallery/test/smoke_test.dart b/examples/material_gallery/test/smoke_test.dart
index 354f4a5..20224d1 100644
--- a/examples/material_gallery/test/smoke_test.dart
+++ b/examples/material_gallery/test/smoke_test.dart
@@ -14,14 +14,14 @@
const List<String> demoCategories = const <String>['Demos', 'Components', 'Style'];
Finder findGalleryItemByRouteName(WidgetTester tester, String routeName) {
- return find.byPredicate((Widget widget) {
+ return find.byWidgetPredicate((Widget widget) {
return widget is material_gallery.GalleryItem
&& widget.routeName == routeName;
});
}
Finder byTooltip(WidgetTester tester, String message) {
- return find.byPredicate((Widget widget) {
+ return find.byWidgetPredicate((Widget widget) {
return widget is Tooltip && widget.message == message;
});
}
diff --git a/packages/flutter/test/widget/multichild_test.dart b/packages/flutter/test/widget/multichild_test.dart
index 9e2a39f..ce43fa5 100644
--- a/packages/flutter/test/widget/multichild_test.dart
+++ b/packages/flutter/test/widget/multichild_test.dart
@@ -10,8 +10,9 @@
import 'test_widgets.dart';
void checkTree(WidgetTester tester, List<BoxDecoration> expectedDecorations) {
- MultiChildRenderObjectElement element =
- tester.elementOf(find.byElement((Element element) => element is MultiChildRenderObjectElement));
+ MultiChildRenderObjectElement element = tester.elementOf(find.byElementPredicate(
+ (Element element) => element is MultiChildRenderObjectElement
+ ));
expect(element, isNotNull);
expect(element.renderObject is RenderStack, isTrue);
RenderStack renderObject = element.renderObject;
diff --git a/packages/flutter/test/widget/parent_data_test.dart b/packages/flutter/test/widget/parent_data_test.dart
index da57f96..956371a 100644
--- a/packages/flutter/test/widget/parent_data_test.dart
+++ b/packages/flutter/test/widget/parent_data_test.dart
@@ -19,8 +19,9 @@
}
void checkTree(WidgetTester tester, List<TestParentData> expectedParentData) {
- MultiChildRenderObjectElement element =
- tester.elementOf(find.byElement((Element element) => element is MultiChildRenderObjectElement));
+ MultiChildRenderObjectElement element = tester.elementOf(
+ find.byElementPredicate((Element element) => element is MultiChildRenderObjectElement)
+ );
expect(element, isNotNull);
expect(element.renderObject is RenderStack, isTrue);
RenderStack renderObject = element.renderObject;
diff --git a/packages/flutter/test/widget/stateful_component_test.dart b/packages/flutter/test/widget/stateful_component_test.dart
index 428a3d1..778219c 100644
--- a/packages/flutter/test/widget/stateful_component_test.dart
+++ b/packages/flutter/test/widget/stateful_component_test.dart
@@ -14,8 +14,9 @@
testWidgets((WidgetTester tester) {
void checkTree(BoxDecoration expectedDecoration) {
- SingleChildRenderObjectElement element =
- tester.elementOf(find.byElement((Element element) => element is SingleChildRenderObjectElement));
+ SingleChildRenderObjectElement element = tester.elementOf(
+ find.byElementPredicate((Element element) => element is SingleChildRenderObjectElement)
+ );
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
diff --git a/packages/flutter_test/lib/src/instrumentation.dart b/packages/flutter_test/lib/src/instrumentation.dart
index d0d774e..fb35554 100644
--- a/packages/flutter_test/lib/src/instrumentation.dart
+++ b/packages/flutter_test/lib/src/instrumentation.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:collection';
-
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
@@ -63,20 +61,6 @@
return null;
}
- /// Returns all elements ordered in a depth-first traversal fashion.
- ///
- /// The returned iterable is lazy. It does not walk the entire element tree
- /// immediately, but rather a chunk at a time as the iteration progresses
- /// using [Iterator.moveNext].
- Iterable<Element> get allElements {
- return new _DepthFirstChildIterable(binding.renderViewElement);
- }
-
- /// Returns all elements that satisfy [predicate].
- Iterable<Element> findElements(bool predicate(Element element)) {
- return allElements.where(predicate);
- }
-
/// Returns the first element that corresponds to a widget with the
/// given [Key], or null if there is no such element.
Element findElementByKey(Key key) {
@@ -248,42 +232,3 @@
return result;
}
}
-
-class _DepthFirstChildIterable extends IterableBase<Element> {
- _DepthFirstChildIterable(this.rootElement);
-
- Element rootElement;
-
- @override
- Iterator<Element> get iterator => new _DepthFirstChildIterator(rootElement);
-}
-
-class _DepthFirstChildIterator implements Iterator<Element> {
- _DepthFirstChildIterator(Element rootElement)
- : _stack = _reverseChildrenOf(rootElement).toList();
-
- Element _current;
-
- final List<Element> _stack;
-
- @override
- Element get current => _current;
-
- @override
- bool moveNext() {
- if (_stack.isEmpty)
- return false;
-
- _current = _stack.removeLast();
- // Stack children in reverse order to traverse first branch first
- _stack.addAll(_reverseChildrenOf(_current));
-
- return true;
- }
-
- static Iterable<Element> _reverseChildrenOf(Element element) {
- List<Element> children = <Element>[];
- element.visitChildren(children.add);
- return children.reversed;
- }
-}
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index faac1c5..aa84e12 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:collection';
+
+import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:quiver/testing/async.dart';
@@ -10,6 +13,12 @@
import 'binding.dart';
import 'test_pointer.dart';
+/// Signature for [CommonFinders.byPredicate].
+typedef bool WidgetPredicate(Widget widget);
+
+/// Signature for [CommonFinders.byElement].
+typedef bool ElementPredicate(Element element);
+
/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
@@ -30,26 +39,6 @@
});
}
-/// A convenient accessor to frequently used finders.
-///
-/// Examples:
-///
-/// tester.tap(find.text('Save'));
-/// tester.widget(find.byType(MyWidget));
-/// tester.stateOf(find.byConfig(config));
-/// tester.getSize(find.byKey(new ValueKey('save-button')));
-const CommonFinders find = const CommonFinders._();
-
-/// Asserts that [finder] locates a widget in the test element tree.
-///
-/// Example:
-///
-/// expect(tester, hasWidget(find.text('Save')));
-Matcher hasWidget(Finder finder) => new _HasWidgetMatcher(finder);
-
-/// Opposite of [hasWidget].
-Matcher doesNotHaveWidget(Finder finder) => new _DoesNotHaveWidgetMatcher(finder);
-
/// Class that programmatically interacts with widgets and the test environment.
class WidgetTester {
WidgetTester._(this.elementTreeTester);
@@ -57,6 +46,8 @@
/// Exposes the [Element] tree created from widgets.
final ElementTreeTester elementTreeTester;
+ /// The binding instance that the widget tester is using when it
+ /// needs a binding (e.g. for event dispatch).
Widgeteer get binding => elementTreeTester.binding;
/// Renders the UI from the given [widget].
@@ -114,8 +105,7 @@
/// All widgets currently live on the UI returned in a depth-first traversal
/// order.
Iterable<Widget> get widgets {
- return this.elementTreeTester.allElements
- .map((Element element) => element.widget);
+ return this.allElements.map((Element element) => element.widget);
}
/// Finds the first widget, searching in the depth-first traversal order.
@@ -233,16 +223,82 @@
Element element = finder.findFirst(this);
return elementTreeTester.getBottomRight(element);
}
+
+ /// Returns all elements ordered in a depth-first traversal fashion.
+ ///
+ /// The returned iterable is lazy. It does not walk the entire element tree
+ /// immediately, but rather a chunk at a time as the iteration progresses
+ /// using [Iterator.moveNext].
+ Iterable<Element> get allElements {
+ return new _DepthFirstChildIterable(binding.renderViewElement);
+ }
}
+class _DepthFirstChildIterable extends IterableBase<Element> {
+ _DepthFirstChildIterable(this.rootElement);
+
+ Element rootElement;
+
+ @override
+ Iterator<Element> get iterator => new _DepthFirstChildIterator(rootElement);
+}
+
+class _DepthFirstChildIterator implements Iterator<Element> {
+ _DepthFirstChildIterator(Element rootElement)
+ : _stack = _reverseChildrenOf(rootElement).toList();
+
+ Element _current;
+
+ final List<Element> _stack;
+
+ @override
+ Element get current => _current;
+
+ @override
+ bool moveNext() {
+ if (_stack.isEmpty)
+ return false;
+
+ _current = _stack.removeLast();
+ // Stack children in reverse order to traverse first branch first
+ _stack.addAll(_reverseChildrenOf(_current));
+
+ return true;
+ }
+
+ static Iterable<Element> _reverseChildrenOf(Element element) {
+ final List<Element> children = <Element>[];
+ element.visitChildren(children.add);
+ return children.reversed;
+ }
+}
+
+/// A convenient accessor to frequently used finders.
+///
+/// Examples:
+///
+/// tester.tap(find.text('Save'));
+/// tester.widget(find.byType(MyWidget));
+/// tester.stateOf(find.byConfig(config));
+/// tester.getSize(find.byKey(new ValueKey('save-button')));
+const CommonFinders find = const CommonFinders._();
+
/// Provides lightweight syntax for getting frequently used widget [Finder]s.
+///
+/// This class is instantiated once, as [find].
class CommonFinders {
const CommonFinders._();
- /// Finds [Text] widgets containing string equal to [text].
+ /// Finds [Text] widgets containing string equal to the `text`
+ /// argument.
+ ///
+ /// Example:
+ ///
+ /// expect(tester, hasWidget(find.text('Back')));
Finder text(String text) => new _TextFinder(text);
- /// Looks for widgets that contain [Text] with [text] in it.
+ /// Looks for widgets that contain a [Text] descendant with `text`
+ /// in it.
///
/// Example:
///
@@ -255,32 +311,67 @@
/// tester.tap(find.widgetWithText(Button, 'Update'));
Finder widgetWithText(Type widgetType, String text) => new _WidgetWithTextFinder(widgetType, text);
- /// Finds widgets by [key].
+ /// Finds widgets by searching for one with a particular [Key].
+ ///
+ /// Example:
+ ///
+ /// expect(tester, hasWidget(find.byKey(backKey)));
Finder byKey(Key key) => new _KeyFinder(key);
- /// Finds widgets by [type].
+ /// Finds widgets by searching for widgehts with a particular type.
+ ///
+ /// The `type` argument must be a subclass of [Widget].
+ ///
+ /// Example:
+ ///
+ /// expect(tester, hasWidget(find.byType(IconButton)));
Finder byType(Type type) => new _TypeFinder(type);
- /// Finds widgets equal to [config].
+ /// Finds widgets whose current widget is the instance given by the
+ /// argument.
+ ///
+ /// Example:
+ ///
+ /// // Suppose you have a button created like this:
+ /// Widget myButton = new Button(
+ /// child: new Text('Update')
+ /// );
+ ///
+ /// // You can find and tap on it like this:
+ /// tester.tap(find.byConfig(myButton));
Finder byConfig(Widget config) => new _ConfigFinder(config);
- /// Finds widgets using a [predicate].
- Finder byPredicate(WidgetPredicate predicate) {
- return new _ElementFinder((Element element) => predicate(element.widget));
- }
+ /// Finds widgets using a widget predicate.
+ ///
+ /// Example:
+ ///
+ /// expect(tester, hasWidget(find.byWidgetPredicate(
+ /// (Widget widget) => widget is Tooltip && widget.message == 'Back'
+ /// )));
+ Finder byWidgetPredicate(WidgetPredicate predicate) => new _WidgetPredicateFinder(predicate);
- /// Finds widgets using an element [predicate].
- Finder byElement(ElementPredicate predicate) => new _ElementFinder(predicate);
+ /// Finds widgets using an element predicate.
+ ///
+ /// Example:
+ ///
+ /// expect(tester, hasWidget(find.byWidgetPredicate(
+ /// (Element element) => element is SingleChildRenderObjectElement
+ /// )));
+ Finder byElementPredicate(ElementPredicate predicate) => new _ElementPredicateFinder(predicate);
}
/// Finds [Element]s inside the element tree.
abstract class Finder {
+ /// Returns all the elements that match this finder's pattern,
+ /// using the given tester to determine which element tree to look at.
Iterable<Element> find(WidgetTester tester);
- /// Describes what the finder is looking for. The description should be such
- /// that [toString] reads as a descriptive English sentence.
+ /// Describes what the finder is looking for. The description should be
+ /// a brief English noun phrase describing the finder's pattern.
String get description;
+ /// Returns the first value returned from [find], unless no value is found,
+ /// in which case it throws an [ElementNotFoundError].
Element findFirst(WidgetTester tester) {
Iterable<Element> results = find(tester);
return results.isNotEmpty
@@ -316,7 +407,7 @@
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.findElements((Element element) {
+ return tester.allElements.where((Element element) {
if (element.widget is! Text)
return false;
Text textWidget = element.widget;
@@ -336,7 +427,7 @@
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.allElements
+ return tester.allElements
.map((Element textElement) {
if (textElement.widget is! Text)
return null;
@@ -370,7 +461,9 @@
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.findElements((Element element) => element.widget.key == key);
+ return tester.allElements.where((Element element) {
+ return element.widget.key == key;
+ });
}
}
@@ -384,7 +477,7 @@
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.allElements.where((Element element) {
+ return tester.allElements.where((Element element) {
return element.widget.runtimeType == widgetType;
});
}
@@ -400,29 +493,49 @@
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.allElements.where((Element element) {
+ return tester.allElements.where((Element element) {
return element.widget == config;
});
}
}
-typedef bool WidgetPredicate(Widget element);
-typedef bool ElementPredicate(Element element);
+class _WidgetPredicateFinder extends Finder {
+ _WidgetPredicateFinder(this.predicate);
-class _ElementFinder extends Finder {
- _ElementFinder(this.predicate);
+ final WidgetPredicate predicate;
+
+ @override
+ String get description => 'widget predicate ($predicate)';
+
+ @override
+ Iterable<Element> find(WidgetTester tester) {
+ return tester.allElements.where((Element element) {
+ return predicate(element.widget);
+ });
+ }
+}
+
+class _ElementPredicateFinder extends Finder {
+ _ElementPredicateFinder(this.predicate);
final ElementPredicate predicate;
@override
- String get description => 'element satisfying given predicate ($predicate)';
+ String get description => 'element predicate ($predicate)';
@override
Iterable<Element> find(WidgetTester tester) {
- return tester.elementTreeTester.allElements.where(predicate);
+ return tester.allElements.where(predicate);
}
}
+/// Asserts that [finder] locates a widget in the test element tree.
+///
+/// Example:
+///
+/// expect(tester, hasWidget(find.text('Save')));
+Matcher hasWidget(Finder finder) => new _HasWidgetMatcher(finder);
+
class _HasWidgetMatcher extends Matcher {
const _HasWidgetMatcher(this.finder);
@@ -445,6 +558,14 @@
}
}
+/// Asserts that [finder] does not locate a widget in the test element tree.
+/// Opposite of [hasWidget].
+///
+/// Example:
+///
+/// expect(tester, doesNotHaveWidget(find.text('Save')));
+Matcher doesNotHaveWidget(Finder finder) => new _DoesNotHaveWidgetMatcher(finder);
+
class _DoesNotHaveWidgetMatcher extends Matcher {
const _DoesNotHaveWidgetMatcher(this.finder);