diff --git a/dev/manual_tests/test/card_collection_test.dart b/dev/manual_tests/test/card_collection_test.dart index 377ce1c3109..ec7760b19f3 100644 --- a/dev/manual_tests/test/card_collection_test.dart +++ b/dev/manual_tests/test/card_collection_test.dart @@ -15,8 +15,7 @@ void main() { 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 354f4a57d94..20224d1cafc 100644 --- a/examples/material_gallery/test/smoke_test.dart +++ b/examples/material_gallery/test/smoke_test.dart @@ -14,14 +14,14 @@ import '../lib/gallery/item.dart' as material_gallery; const List demoCategories = const ['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 9e2a39fca87..ce43fa5ad57 100644 --- a/packages/flutter/test/widget/multichild_test.dart +++ b/packages/flutter/test/widget/multichild_test.dart @@ -10,8 +10,9 @@ import 'package:test/test.dart'; import 'test_widgets.dart'; void checkTree(WidgetTester tester, List 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 da57f9695d2..956371ab802 100644 --- a/packages/flutter/test/widget/parent_data_test.dart +++ b/packages/flutter/test/widget/parent_data_test.dart @@ -19,8 +19,9 @@ class TestParentData { } void checkTree(WidgetTester tester, List 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 428a3d13187..778219c58f0 100644 --- a/packages/flutter/test/widget/stateful_component_test.dart +++ b/packages/flutter/test/widget/stateful_component_test.dart @@ -14,8 +14,9 @@ void main() { 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 d0d774e6cc6..fb35554d3e2 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 @@ class Instrumentation { 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 get allElements { - return new _DepthFirstChildIterable(binding.renderViewElement); - } - - /// Returns all elements that satisfy [predicate]. - Iterable 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 @@ class Instrumentation { return result; } } - -class _DepthFirstChildIterable extends IterableBase { - _DepthFirstChildIterable(this.rootElement); - - Element rootElement; - - @override - Iterator get iterator => new _DepthFirstChildIterator(rootElement); -} - -class _DepthFirstChildIterator implements Iterator { - _DepthFirstChildIterator(Element rootElement) - : _stack = _reverseChildrenOf(rootElement).toList(); - - Element _current; - - final List _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 _reverseChildrenOf(Element element) { - List children = []; - 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 faac1c5b21c..aa84e1252e4 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 'package:test/test.dart'; 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 @@ void testWidgets(void callback(WidgetTester widgetTester)) { }); } -/// 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 @@ class WidgetTester { /// 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 @@ class WidgetTester { /// All widgets currently live on the UI returned in a depth-first traversal /// order. Iterable 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 @@ class WidgetTester { 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 get allElements { + return new _DepthFirstChildIterable(binding.renderViewElement); + } } +class _DepthFirstChildIterable extends IterableBase { + _DepthFirstChildIterable(this.rootElement); + + Element rootElement; + + @override + Iterator get iterator => new _DepthFirstChildIterator(rootElement); +} + +class _DepthFirstChildIterator implements Iterator { + _DepthFirstChildIterator(Element rootElement) + : _stack = _reverseChildrenOf(rootElement).toList(); + + Element _current; + + final List _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 _reverseChildrenOf(Element element) { + final List children = []; + 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 @@ class CommonFinders { /// 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 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 results = find(tester); return results.isNotEmpty @@ -316,7 +407,7 @@ class _TextFinder extends Finder { @override Iterable 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 @@ class _WidgetWithTextFinder extends Finder { @override Iterable find(WidgetTester tester) { - return tester.elementTreeTester.allElements + return tester.allElements .map((Element textElement) { if (textElement.widget is! Text) return null; @@ -370,7 +461,9 @@ class _KeyFinder extends Finder { @override Iterable 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 @@ class _TypeFinder extends Finder { @override Iterable 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 @@ class _ConfigFinder extends Finder { @override Iterable 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 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 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 @@ class _HasWidgetMatcher extends Matcher { } } +/// 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);