diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index 4e815513a51..73f8f90f370 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -198,6 +198,29 @@ class CommonFinders { Finder descendant({ Finder of, Finder matching, bool matchRoot: false, bool skipOffstage: true }) { return new _DescendantFinder(of, matching, matchRoot: matchRoot, skipOffstage: skipOffstage); } + + /// Finds widgets that are ancestors of the [of] parameter and that match + /// the [matching] parameter. + /// + /// Example: + /// + /// // Test if a Text widget that contains 'faded' is the + /// // descendant of an Opacity widget with opacity 0.5: + /// expect( + /// tester.widget( + /// find.ancestor( + /// of: find.text('faded'), + /// matching: find.byType('Opacity'), + /// ) + /// ).opacity, + /// 0.5 + /// ); + /// + /// If the [matchRoot] argument is true then the widget(s) specified by [of] + /// will be matched along with the ancestors. + Finder ancestor({ Finder of, Finder matching, bool matchRoot: false}) { + return new _AncestorFinder(of, matching, matchRoot: matchRoot); + } } /// Searches a widget tree and returns nodes that match a particular @@ -583,3 +606,39 @@ class _DescendantFinder extends Finder { return candidates; } } + +class _AncestorFinder extends Finder { + _AncestorFinder(this.descendant, this.ancestor, { this.matchRoot: false }) : super(skipOffstage: false); + + final Finder ancestor; + final Finder descendant; + final bool matchRoot; + + @override + String get description { + if (matchRoot) + return 'ancestor ${ancestor.description} beginning with ${descendant.description}'; + return '${ancestor.description} which is an ancestor of ${descendant.description}'; + } + + @override + Iterable apply(Iterable candidates) { + return candidates.where((Element element) => ancestor.evaluate().contains(element)); + } + + @override + Iterable get allCandidates { + final List candidates = []; + for (Element root in descendant.evaluate()) { + final List ancestors = []; + if (matchRoot) + ancestors.add(root); + root.visitAncestorElements((Element element) { + ancestors.add(element); + return true; + }); + candidates.addAll(ancestors); + } + return candidates; + } +} diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index af4903b6b0b..85e3800580a 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -198,6 +198,66 @@ void main() { contains('Actual: ?:[ + new Column(children: fooBarTexts), + ], + )); + + expect(find.ancestor( + of: find.text('bar'), + matching: find.widgetWithText(Row, 'foo'), + ), findsOneWidget); + }); + + testWidgets('finds two matching ancestors, one descendant', (WidgetTester tester) async { + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new Row( + children: [ + new Row(children: fooBarTexts), + ], + ), + ), + ); + + expect(find.ancestor( + of: find.text('bar'), + matching: find.byType(Row), + ), findsNWidgets(2)); + }); + + testWidgets('fails with a descriptive message', (WidgetTester tester) async { + await tester.pumpWidget(new Row( + textDirection: TextDirection.ltr, + children: [ + new Column(children: [const Text('foo', textDirection: TextDirection.ltr)]), + const Text('bar', textDirection: TextDirection.ltr), + ], + )); + + TestFailure failure; + try { + expect(find.ancestor( + of: find.text('bar'), + matching: find.widgetWithText(Column, 'foo'), + ), findsOneWidget); + } catch (e) { + failure = e; + } + + expect(failure, isNotNull); + expect( + failure.message, + contains('Actual: ?: