Commit 47666af5 authored by zhenqiu1101's avatar zhenqiu1101 Committed by Ian Hickson

Add find.descendant API to support find descendants of an element by passing...

Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant. (#7944)

* Add find.descendant API to support find descendants of an element by passing the current element and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.

* Add find.descendant API to support find descendants of an element by passing the current element finder and the finder of descendant.
parent 80956517
...@@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; ...@@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
import 'all_elements.dart'; import 'all_elements.dart';
import 'package:meta/meta.dart';
/// Signature for [CommonFinders.byPredicate]. /// Signature for [CommonFinders.byPredicate].
typedef bool WidgetPredicate(Widget widget); typedef bool WidgetPredicate(Widget widget);
...@@ -26,7 +28,7 @@ class CommonFinders { ...@@ -26,7 +28,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.text('Back'))); /// expect(find.text('Back'), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -37,7 +39,7 @@ class CommonFinders { ...@@ -37,7 +39,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.icon(Icons.chevron_left))); /// expect(find.icon(Icons.chevron_left), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -66,7 +68,7 @@ class CommonFinders { ...@@ -66,7 +68,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byKey(backKey))); /// expect(find.byKey(backKey), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -82,7 +84,7 @@ class CommonFinders { ...@@ -82,7 +84,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byType(IconButton))); /// expect(find.byType(IconButton), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -98,7 +100,7 @@ class CommonFinders { ...@@ -98,7 +100,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byElementType(SingleChildRenderObjectElement))); /// expect(find.byElementType(SingleChildRenderObjectElement), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -125,10 +127,10 @@ class CommonFinders { ...@@ -125,10 +127,10 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byWidgetPredicate( /// expect(find.byWidgetPredicate(
/// (Widget widget) => widget is Tooltip && widget.message == 'Back', /// (Widget widget) => widget is Tooltip && widget.message == 'Back',
/// description: 'widget with tooltip "Back"', /// description: 'widget with tooltip "Back"',
/// ))); /// ), findsOneWidget);
/// ///
/// If [description] is provided, then this uses it as the description of the /// If [description] is provided, then this uses it as the description of the
/// [Finder] and appears, for example, in the error message when the finder /// [Finder] and appears, for example, in the error message when the finder
...@@ -145,7 +147,7 @@ class CommonFinders { ...@@ -145,7 +147,7 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byTooltip('Back'))); /// expect(find.byTooltip('Back'), findsOneWidget);
/// ///
/// If the `skipOffstage` argument is true (the default), then this skips /// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
...@@ -160,13 +162,13 @@ class CommonFinders { ...@@ -160,13 +162,13 @@ class CommonFinders {
/// ///
/// Example: /// Example:
/// ///
/// expect(tester, hasWidget(find.byElementPredicate( /// expect(find.byElementPredicate(
/// // finds elements of type SingleChildRenderObjectElement, including /// // finds elements of type SingleChildRenderObjectElement, including
/// // those that are actually subclasses of that type. /// // those that are actually subclasses of that type.
/// // (contrast with byElementType, which only returns exact matches) /// // (contrast with byElementType, which only returns exact matches)
/// (Element element) => element is SingleChildRenderObjectElement, /// (Element element) => element is SingleChildRenderObjectElement,
/// description: '$SingleChildRenderObjectElement element', /// description: '$SingleChildRenderObjectElement element',
/// ))); /// ), findsOneWidget);
/// ///
/// If [description] is provided, then this uses it as the description of the /// If [description] is provided, then this uses it as the description of the
/// [Finder] and appears, for example, in the error message when the finder /// [Finder] and appears, for example, in the error message when the finder
...@@ -178,6 +180,21 @@ class CommonFinders { ...@@ -178,6 +180,21 @@ class CommonFinders {
Finder byElementPredicate(ElementPredicate predicate, { String description, bool skipOffstage: true }) { Finder byElementPredicate(ElementPredicate predicate, { String description, bool skipOffstage: true }) {
return new _ElementPredicateFinder(predicate, description: description, skipOffstage: skipOffstage); return new _ElementPredicateFinder(predicate, description: description, skipOffstage: skipOffstage);
} }
/// Looks for widgets that match the pattern of descendant finder under the
/// widget tree with ancestor as the root.
///
/// Example:
///
/// expect(find.descendant(
/// of: find.widgetWithText(Row, 'label_1'), matching: find.text('value_1')
/// ), findsOneWidget);
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
Finder descendant({ Finder of, Finder matching, bool skipOffstage: true }) {
return new _DescendantFinder(of, matching, skipOffstage: skipOffstage);
}
} }
/// Searches a widget tree and returns nodes that match a particular /// Searches a widget tree and returns nodes that match a particular
...@@ -207,7 +224,8 @@ abstract class Finder { ...@@ -207,7 +224,8 @@ abstract class Finder {
/// [Offstage] widgets, as well as children of inactive [Route]s. /// [Offstage] widgets, as well as children of inactive [Route]s.
final bool skipOffstage; final bool skipOffstage;
Iterable<Element> get _allElements { @protected
Iterable<Element> get allCandidates {
return collectAllElementsFrom( return collectAllElementsFrom(
WidgetsBinding.instance.renderViewElement, WidgetsBinding.instance.renderViewElement,
skipOffstage: skipOffstage skipOffstage: skipOffstage
...@@ -222,7 +240,7 @@ abstract class Finder { ...@@ -222,7 +240,7 @@ abstract class Finder {
/// ///
/// Calling this clears the cache from [precache]. /// Calling this clears the cache from [precache].
Iterable<Element> evaluate() { Iterable<Element> evaluate() {
final Iterable<Element> result = _cachedResult ?? apply(_allElements); final Iterable<Element> result = _cachedResult ?? apply(allCandidates);
_cachedResult = null; _cachedResult = null;
return result; return result;
} }
...@@ -234,7 +252,7 @@ abstract class Finder { ...@@ -234,7 +252,7 @@ abstract class Finder {
/// If this returns true, you must call [evaluate] before you call [precache] again. /// If this returns true, you must call [evaluate] before you call [precache] again.
bool precache() { bool precache() {
assert(_cachedResult == null); assert(_cachedResult == null);
final Iterable<Element> result = apply(_allElements); final Iterable<Element> result = apply(allCandidates);
if (result.isNotEmpty) { if (result.isNotEmpty) {
_cachedResult = result; _cachedResult = result;
return true; return true;
...@@ -469,3 +487,25 @@ class _ElementPredicateFinder extends MatchFinder { ...@@ -469,3 +487,25 @@ class _ElementPredicateFinder extends MatchFinder {
return predicate(candidate); return predicate(candidate);
} }
} }
class _DescendantFinder extends Finder {
_DescendantFinder(this.ancestor, this.descendant, { bool skipOffstage: true }) : super(skipOffstage: skipOffstage);
final Finder ancestor;
final Finder descendant;
@override
String get description => '${descendant.description} that has ancestor(s) with ${ancestor.description} ';
@override
Iterable<Element> apply(Iterable<Element> candidates) {
return candidates.where((Element element) => descendant.evaluate().contains(element));
}
@override
Iterable<Element> get allCandidates {
return ancestor.evaluate().expand(
(Element element) => collectAllElementsFrom(element, skipOffstage: skipOffstage)
).toSet().toList();
}
}
...@@ -137,4 +137,52 @@ void main() { ...@@ -137,4 +137,52 @@ void main() {
expect(failure.message, contains('Actual: ?:<zero widgets with $customDescription')); expect(failure.message, contains('Actual: ?:<zero widgets with $customDescription'));
}); });
}); });
group('find.descendant', () {
testWidgets('finds one descendant', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[
new Column(children: <Text>[new Text('foo'), new Text('bar')])
]));
expect(find.descendant(
of: find.widgetWithText(Row, 'foo'),
matching: find.text('bar')
), findsOneWidget);
});
testWidgets('finds two descendants with different ancestors', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[
new Column(children: <Text>[new Text('foo'), new Text('bar')]),
new Column(children: <Text>[new Text('foo'), new Text('bar')]),
]));
expect(find.descendant(
of: find.widgetWithText(Column, 'foo'),
matching: find.text('bar')
), findsNWidgets(2));
});
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[
new Column(children: <Text>[new Text('foo')]),
new Text('bar')
]));
TestFailure failure;
try {
expect(find.descendant(
of: find.widgetWithText(Column, 'foo'),
matching: find.text('bar')
), findsOneWidget);
} catch (e) {
failure = e;
}
expect(failure, isNotNull);
expect(
failure.message,
contains('Actual: ?:<zero widgets with text "bar" that has ancestor(s) with type Column with text "foo"')
);
});
});
} }
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