From 0f4ea504bc87c865cc701623e194ef65f3b267d5 Mon Sep 17 00:00:00 2001 From: xster Date: Wed, 15 Aug 2018 17:24:03 -0700 Subject: [PATCH] Correctly chain finders to ancestor/descendant finders (#20601) --- .../test/material/input_decorator_test.dart | 2 +- packages/flutter_test/lib/src/finders.dart | 58 ++++++++++++------- packages/flutter_test/test/finders_test.dart | 28 +++++++++ 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index a7d564aa8fb..75586a5b820 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -86,7 +86,7 @@ double getHintOpacity(WidgetTester tester) { find.ancestor( of: find.text('hint'), matching: find.byType(FadeTransition), - ).last + ).first ); return opacityWidget.opacity.value; } diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index 59bdbf4beb5..2133d0791fe 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -375,38 +375,55 @@ abstract class Finder { } } -class _FirstFinder extends Finder { - _FirstFinder(this.parent); +/// Applies additional filtering against a [parent] [Finder]. +abstract class ChainedFinder extends Finder { + /// Create a Finder chained against the candidates of another [Finder]. + ChainedFinder(this.parent) : assert(parent != null); + /// Another [Finder] that will run first. final Finder parent; + /// Return another [Iterable] when given an [Iterable] of candidates from a + /// parent [Finder]. + /// + /// This is the method to implement when subclassing [ChainedFinder]. + Iterable filter(Iterable parentCandidates); + + @override + Iterable apply(Iterable candidates) { + return filter(parent.apply(candidates)); + } + + @override + Iterable get allCandidates => parent.allCandidates; +} + +class _FirstFinder extends ChainedFinder { + _FirstFinder(Finder parent) : super(parent); + @override String get description => '${parent.description} (ignoring all but first)'; @override - Iterable apply(Iterable candidates) sync* { - yield parent.apply(candidates).first; + Iterable filter(Iterable parentCandidates) sync* { + yield parentCandidates.first; } } -class _LastFinder extends Finder { - _LastFinder(this.parent); - - final Finder parent; +class _LastFinder extends ChainedFinder { + _LastFinder(Finder parent) : super(parent); @override String get description => '${parent.description} (ignoring all but last)'; @override - Iterable apply(Iterable candidates) sync* { - yield parent.apply(candidates).last; + Iterable filter(Iterable parentCandidates) sync* { + yield parentCandidates.last; } } -class _IndexFinder extends Finder { - _IndexFinder(this.parent, this.index); - - final Finder parent; +class _IndexFinder extends ChainedFinder { + _IndexFinder(Finder parent, this.index) : super(parent); final int index; @@ -414,23 +431,22 @@ class _IndexFinder extends Finder { String get description => '${parent.description} (ignoring all but index $index)'; @override - Iterable apply(Iterable candidates) sync* { - yield parent.apply(candidates).elementAt(index); + Iterable filter(Iterable parentCandidates) sync* { + yield parentCandidates.elementAt(index); } } -class _HitTestableFinder extends Finder { - _HitTestableFinder(this.parent, this.alignment); +class _HitTestableFinder extends ChainedFinder { + _HitTestableFinder(Finder parent, this.alignment) : super(parent); - final Finder parent; final Alignment alignment; @override String get description => '${parent.description} (considering only hit-testable ones)'; @override - Iterable apply(Iterable candidates) sync* { - for (final Element candidate in parent.apply(candidates)) { + Iterable filter(Iterable parentCandidates) sync* { + for (final Element candidate in parentCandidates) { final RenderBox box = candidate.renderObject; assert(box != null); final Offset absoluteOffset = box.localToGlobal(alignment.alongSize(box.size)); diff --git a/packages/flutter_test/test/finders_test.dart b/packages/flutter_test/test/finders_test.dart index 0f603d76f56..a42925b55f9 100644 --- a/packages/flutter_test/test/finders_test.dart +++ b/packages/flutter_test/test/finders_test.dart @@ -56,6 +56,34 @@ void main() { expect(tester.widget(hitTestable).key, const ValueKey(0)); }); }); + + testWidgets('ChainedFinders chain properly', (WidgetTester tester) async { + final GlobalKey key1 = new GlobalKey(); + await tester.pumpWidget( + _boilerplate(new Column( + children: [ + new Container( + key: key1, + child: const Text('1'), + ), + new Container( + child: const Text('2'), + ) + ], + )), + ); + + // Get the text back. By correctly chaining the descendant finder's + // candidates, it should find 1 instead of 2. If the _LastFinder wasn't + // correctly chained after the descendant's candidates, the last element + // with a Text widget would have been 2. + final Text text = find.descendant( + of: find.byKey(key1), + matching: find.byType(Text), + ).last.evaluate().single.widget; + + expect(text.data, '1'); + }); } Widget _boilerplate(Widget child) {