• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/gestures.dart';
6import 'package:flutter/material.dart';
7import 'package:meta/meta.dart';
8
9import 'all_elements.dart';
10
11/// Signature for [CommonFinders.byWidgetPredicate].
12typedef WidgetPredicate = bool Function(Widget widget);
13
14/// Signature for [CommonFinders.byElementPredicate].
15typedef ElementPredicate = bool Function(Element element);
16
17/// Some frequently used widget [Finder]s.
18const CommonFinders find = CommonFinders._();
19
20/// Provides lightweight syntax for getting frequently used widget [Finder]s.
21///
22/// This class is instantiated once, as [find].
23class CommonFinders {
24  const CommonFinders._();
25
26  /// Finds [Text] and [EditableText] widgets containing string equal to the
27  /// `text` argument.
28  ///
29  /// ## Sample code
30  ///
31  /// ```dart
32  /// expect(find.text('Back'), findsOneWidget);
33  /// ```
34  ///
35  /// If the `skipOffstage` argument is true (the default), then this skips
36  /// nodes that are [Offstage] or that are from inactive [Route]s.
37  Finder text(String text, { bool skipOffstage = true }) => _TextFinder(text, skipOffstage: skipOffstage);
38
39  /// Looks for widgets that contain a [Text] descendant with `text`
40  /// in it.
41  ///
42  /// ## Sample code
43  ///
44  /// ```dart
45  /// // Suppose you have a button with text 'Update' in it:
46  /// new Button(
47  ///   child: new Text('Update')
48  /// )
49  ///
50  /// // You can find and tap on it like this:
51  /// tester.tap(find.widgetWithText(Button, 'Update'));
52  /// ```
53  ///
54  /// If the `skipOffstage` argument is true (the default), then this skips
55  /// nodes that are [Offstage] or that are from inactive [Route]s.
56  Finder widgetWithText(Type widgetType, String text, { bool skipOffstage = true }) {
57    return find.ancestor(
58      of: find.text(text, skipOffstage: skipOffstage),
59      matching: find.byType(widgetType, skipOffstage: skipOffstage),
60    );
61  }
62
63  /// Finds widgets by searching for one with a particular [Key].
64  ///
65  /// ## Sample code
66  ///
67  /// ```dart
68  /// expect(find.byKey(backKey), findsOneWidget);
69  /// ```
70  ///
71  /// If the `skipOffstage` argument is true (the default), then this skips
72  /// nodes that are [Offstage] or that are from inactive [Route]s.
73  Finder byKey(Key key, { bool skipOffstage = true }) => _KeyFinder(key, skipOffstage: skipOffstage);
74
75  /// Finds widgets by searching for widgets with a particular type.
76  ///
77  /// This does not do subclass tests, so for example
78  /// `byType(StatefulWidget)` will never find anything since that's
79  /// an abstract class.
80  ///
81  /// The `type` argument must be a subclass of [Widget].
82  ///
83  /// ## Sample code
84  ///
85  /// ```dart
86  /// expect(find.byType(IconButton), findsOneWidget);
87  /// ```
88  ///
89  /// If the `skipOffstage` argument is true (the default), then this skips
90  /// nodes that are [Offstage] or that are from inactive [Route]s.
91  Finder byType(Type type, { bool skipOffstage = true }) => _WidgetTypeFinder(type, skipOffstage: skipOffstage);
92
93  /// Finds [Icon] widgets containing icon data equal to the `icon`
94  /// argument.
95  ///
96  /// ## Sample code
97  ///
98  /// ```dart
99  /// expect(find.byIcon(Icons.inbox), findsOneWidget);
100  /// ```
101  ///
102  /// If the `skipOffstage` argument is true (the default), then this skips
103  /// nodes that are [Offstage] or that are from inactive [Route]s.
104  Finder byIcon(IconData icon, { bool skipOffstage = true }) => _WidgetIconFinder(icon, skipOffstage: skipOffstage);
105
106  /// Looks for widgets that contain an [Icon] descendant displaying [IconData]
107  /// `icon` in it.
108  ///
109  /// ## Sample code
110  ///
111  /// ```dart
112  /// // Suppose you have a button with icon 'arrow_forward' in it:
113  /// new Button(
114  ///   child: new Icon(Icons.arrow_forward)
115  /// )
116  ///
117  /// // You can find and tap on it like this:
118  /// tester.tap(find.widgetWithIcon(Button, Icons.arrow_forward));
119  /// ```
120  ///
121  /// If the `skipOffstage` argument is true (the default), then this skips
122  /// nodes that are [Offstage] or that are from inactive [Route]s.
123  Finder widgetWithIcon(Type widgetType, IconData icon, { bool skipOffstage = true }) {
124    return find.ancestor(
125      of: find.byIcon(icon),
126      matching: find.byType(widgetType),
127    );
128  }
129
130  /// Finds widgets by searching for elements with a particular type.
131  ///
132  /// This does not do subclass tests, so for example
133  /// `byElementType(VirtualViewportElement)` will never find anything
134  /// since that's an abstract class.
135  ///
136  /// The `type` argument must be a subclass of [Element].
137  ///
138  /// ## Sample code
139  ///
140  /// ```dart
141  /// expect(find.byElementType(SingleChildRenderObjectElement), findsOneWidget);
142  /// ```
143  ///
144  /// If the `skipOffstage` argument is true (the default), then this skips
145  /// nodes that are [Offstage] or that are from inactive [Route]s.
146  Finder byElementType(Type type, { bool skipOffstage = true }) => _ElementTypeFinder(type, skipOffstage: skipOffstage);
147
148  /// Finds widgets whose current widget is the instance given by the
149  /// argument.
150  ///
151  /// ## Sample code
152  ///
153  /// ```dart
154  /// // Suppose you have a button created like this:
155  /// Widget myButton = new Button(
156  ///   child: new Text('Update')
157  /// );
158  ///
159  /// // You can find and tap on it like this:
160  /// tester.tap(find.byWidget(myButton));
161  /// ```
162  ///
163  /// If the `skipOffstage` argument is true (the default), then this skips
164  /// nodes that are [Offstage] or that are from inactive [Route]s.
165  Finder byWidget(Widget widget, { bool skipOffstage = true }) => _WidgetFinder(widget, skipOffstage: skipOffstage);
166
167  /// Finds widgets using a widget [predicate].
168  ///
169  /// ## Sample code
170  ///
171  /// ```dart
172  /// expect(find.byWidgetPredicate(
173  ///   (Widget widget) => widget is Tooltip && widget.message == 'Back',
174  ///   description: 'widget with tooltip "Back"',
175  /// ), findsOneWidget);
176  /// ```
177  ///
178  /// If [description] is provided, then this uses it as the description of the
179  /// [Finder] and appears, for example, in the error message when the finder
180  /// fails to locate the desired widget. Otherwise, the description prints the
181  /// signature of the predicate function.
182  ///
183  /// If the `skipOffstage` argument is true (the default), then this skips
184  /// nodes that are [Offstage] or that are from inactive [Route]s.
185  Finder byWidgetPredicate(WidgetPredicate predicate, { String description, bool skipOffstage = true }) {
186    return _WidgetPredicateFinder(predicate, description: description, skipOffstage: skipOffstage);
187  }
188
189  /// Finds Tooltip widgets with the given message.
190  ///
191  /// ## Sample code
192  ///
193  /// ```dart
194  /// expect(find.byTooltip('Back'), findsOneWidget);
195  /// ```
196  ///
197  /// If the `skipOffstage` argument is true (the default), then this skips
198  /// nodes that are [Offstage] or that are from inactive [Route]s.
199  Finder byTooltip(String message, { bool skipOffstage = true }) {
200    return byWidgetPredicate(
201      (Widget widget) => widget is Tooltip && widget.message == message,
202      skipOffstage: skipOffstage,
203    );
204  }
205
206  /// Finds widgets using an element [predicate].
207  ///
208  /// ## Sample code
209  ///
210  /// ```dart
211  /// expect(find.byElementPredicate(
212  ///   // finds elements of type SingleChildRenderObjectElement, including
213  ///   // those that are actually subclasses of that type.
214  ///   // (contrast with byElementType, which only returns exact matches)
215  ///   (Element element) => element is SingleChildRenderObjectElement,
216  ///   description: '$SingleChildRenderObjectElement element',
217  /// ), findsOneWidget);
218  /// ```
219  ///
220  /// If [description] is provided, then this uses it as the description of the
221  /// [Finder] and appears, for example, in the error message when the finder
222  /// fails to locate the desired widget. Otherwise, the description prints the
223  /// signature of the predicate function.
224  ///
225  /// If the `skipOffstage` argument is true (the default), then this skips
226  /// nodes that are [Offstage] or that are from inactive [Route]s.
227  Finder byElementPredicate(ElementPredicate predicate, { String description, bool skipOffstage = true }) {
228    return _ElementPredicateFinder(predicate, description: description, skipOffstage: skipOffstage);
229  }
230
231  /// Finds widgets that are descendants of the [of] parameter and that match
232  /// the [matching] parameter.
233  ///
234  /// ## Sample code
235  ///
236  /// ```dart
237  /// expect(find.descendant(
238  ///   of: find.widgetWithText(Row, 'label_1'), matching: find.text('value_1')
239  /// ), findsOneWidget);
240  /// ```
241  ///
242  /// If the [matchRoot] argument is true then the widget(s) specified by [of]
243  /// will be matched along with the descendants.
244  ///
245  /// If the [skipOffstage] argument is true (the default), then nodes that are
246  /// [Offstage] or that are from inactive [Route]s are skipped.
247  Finder descendant({ Finder of, Finder matching, bool matchRoot = false, bool skipOffstage = true }) {
248    return _DescendantFinder(of, matching, matchRoot: matchRoot, skipOffstage: skipOffstage);
249  }
250
251  /// Finds widgets that are ancestors of the [of] parameter and that match
252  /// the [matching] parameter.
253  ///
254  /// ## Sample code
255  ///
256  /// ```dart
257  /// // Test if a Text widget that contains 'faded' is the
258  /// // descendant of an Opacity widget with opacity 0.5:
259  /// expect(
260  ///   tester.widget<Opacity>(
261  ///     find.ancestor(
262  ///       of: find.text('faded'),
263  ///       matching: find.byType('Opacity'),
264  ///     )
265  ///   ).opacity,
266  ///   0.5
267  /// );
268  /// ```
269  ///
270  /// If the [matchRoot] argument is true then the widget(s) specified by [of]
271  /// will be matched along with the ancestors.
272  Finder ancestor({ Finder of, Finder matching, bool matchRoot = false }) {
273    return _AncestorFinder(of, matching, matchRoot: matchRoot);
274  }
275
276  /// Finds [Semantics] widgets matching the given `label`, either by
277  /// [RegExp.hasMatch] or string equality.
278  ///
279  /// The framework may combine semantics labels in certain scenarios, such as
280  /// when multiple [Text] widgets are in a [MaterialButton] widget. In such a
281  /// case, it may be preferable to match by regular expression. Consumers of
282  /// this API __must not__ introduce unsuitable content into the semantics tree
283  /// for the purposes of testing; in particular, you should prefer matching by
284  /// regular expression rather than by string if the framework has combined
285  /// your semantics, and not try to force the framework to break up the
286  /// semantics nodes. Breaking up the nodes would have an undesirable effect on
287  /// screen readers and other accessibility services.
288  ///
289  /// ## Sample code
290  ///
291  /// ```dart
292  /// expect(find.BySemanticsLabel('Back'), findsOneWidget);
293  /// ```
294  ///
295  /// If the `skipOffstage` argument is true (the default), then this skips
296  /// nodes that are [Offstage] or that are from inactive [Route]s.
297  Finder bySemanticsLabel(Pattern label, { bool skipOffstage = true }) {
298    if (WidgetsBinding.instance.pipelineOwner.semanticsOwner == null)
299      throw StateError('Semantics are not enabled. '
300                       'Make sure to call tester.enableSemantics() before using '
301                       'this finder, and call dispose on its return value after.');
302    return byElementPredicate(
303      (Element element) {
304        // Multiple elements can have the same renderObject - we want the "owner"
305        // of the renderObject, i.e. the RenderObjectElement.
306        if (element is! RenderObjectElement) {
307          return false;
308        }
309        final String semanticsLabel = element.renderObject?.debugSemantics?.label;
310        if (semanticsLabel == null) {
311          return false;
312        }
313        return label is RegExp
314            ? label.hasMatch(semanticsLabel)
315            : label == semanticsLabel;
316      },
317      skipOffstage: skipOffstage,
318    );
319  }
320}
321
322/// Searches a widget tree and returns nodes that match a particular
323/// pattern.
324abstract class Finder {
325  /// Initializes a Finder. Used by subclasses to initialize the [skipOffstage]
326  /// property.
327  Finder({ this.skipOffstage = true });
328
329  /// Describes what the finder is looking for. The description should be
330  /// a brief English noun phrase describing the finder's pattern.
331  String get description;
332
333  /// Returns all the elements in the given list that match this
334  /// finder's pattern.
335  ///
336  /// When implementing your own Finders that inherit directly from
337  /// [Finder], this is the main method to override. If your finder
338  /// can efficiently be described just in terms of a predicate
339  /// function, consider extending [MatchFinder] instead.
340  Iterable<Element> apply(Iterable<Element> candidates);
341
342  /// Whether this finder skips nodes that are offstage.
343  ///
344  /// If this is true, then the elements are walked using
345  /// [Element.debugVisitOnstageChildren]. This skips offstage children of
346  /// [Offstage] widgets, as well as children of inactive [Route]s.
347  final bool skipOffstage;
348
349  /// Returns all the [Element]s that will be considered by this finder.
350  ///
351  /// See [collectAllElementsFrom].
352  @protected
353  Iterable<Element> get allCandidates {
354    return collectAllElementsFrom(
355      WidgetsBinding.instance.renderViewElement,
356      skipOffstage: skipOffstage,
357    );
358  }
359
360  Iterable<Element> _cachedResult;
361
362  /// Returns the current result. If [precache] was called and returned true, this will
363  /// cheaply return the result that was computed then. Otherwise, it creates a new
364  /// iterable to compute the answer.
365  ///
366  /// Calling this clears the cache from [precache].
367  Iterable<Element> evaluate() {
368    final Iterable<Element> result = _cachedResult ?? apply(allCandidates);
369    _cachedResult = null;
370    return result;
371  }
372
373  /// Attempts to evaluate the finder. Returns whether any elements in the tree
374  /// matched the finder. If any did, then the result is cached and can be obtained
375  /// from [evaluate].
376  ///
377  /// If this returns true, you must call [evaluate] before you call [precache] again.
378  bool precache() {
379    assert(_cachedResult == null);
380    final Iterable<Element> result = apply(allCandidates);
381    if (result.isNotEmpty) {
382      _cachedResult = result;
383      return true;
384    }
385    _cachedResult = null;
386    return false;
387  }
388
389  /// Returns a variant of this finder that only matches the first element
390  /// matched by this finder.
391  Finder get first => _FirstFinder(this);
392
393  /// Returns a variant of this finder that only matches the last element
394  /// matched by this finder.
395  Finder get last => _LastFinder(this);
396
397  /// Returns a variant of this finder that only matches the element at the
398  /// given index matched by this finder.
399  Finder at(int index) => _IndexFinder(this, index);
400
401  /// Returns a variant of this finder that only matches elements reachable by
402  /// a hit test.
403  ///
404  /// The [at] parameter specifies the location relative to the size of the
405  /// target element where the hit test is performed.
406  Finder hitTestable({ Alignment at = Alignment.center }) => _HitTestableFinder(this, at);
407
408  @override
409  String toString() {
410    final String additional = skipOffstage ? ' (ignoring offstage widgets)' : '';
411    final List<Element> widgets = evaluate().toList();
412    final int count = widgets.length;
413    if (count == 0)
414      return 'zero widgets with $description$additional';
415    if (count == 1)
416      return 'exactly one widget with $description$additional: ${widgets.single}';
417    if (count < 4)
418      return '$count widgets with $description$additional: $widgets';
419    return '$count widgets with $description$additional: ${widgets[0]}, ${widgets[1]}, ${widgets[2]}, ...';
420  }
421}
422
423/// Applies additional filtering against a [parent] [Finder].
424abstract class ChainedFinder extends Finder {
425  /// Create a Finder chained against the candidates of another [Finder].
426  ChainedFinder(this.parent) : assert(parent != null);
427
428  /// Another [Finder] that will run first.
429  final Finder parent;
430
431  /// Return another [Iterable] when given an [Iterable] of candidates from a
432  /// parent [Finder].
433  ///
434  /// This is the method to implement when subclassing [ChainedFinder].
435  Iterable<Element> filter(Iterable<Element> parentCandidates);
436
437  @override
438  Iterable<Element> apply(Iterable<Element> candidates) {
439    return filter(parent.apply(candidates));
440  }
441
442  @override
443  Iterable<Element> get allCandidates => parent.allCandidates;
444}
445
446class _FirstFinder extends ChainedFinder {
447  _FirstFinder(Finder parent) : super(parent);
448
449  @override
450  String get description => '${parent.description} (ignoring all but first)';
451
452  @override
453  Iterable<Element> filter(Iterable<Element> parentCandidates) sync* {
454    yield parentCandidates.first;
455  }
456}
457
458class _LastFinder extends ChainedFinder {
459  _LastFinder(Finder parent) : super(parent);
460
461  @override
462  String get description => '${parent.description} (ignoring all but last)';
463
464  @override
465  Iterable<Element> filter(Iterable<Element> parentCandidates) sync* {
466    yield parentCandidates.last;
467  }
468}
469
470class _IndexFinder extends ChainedFinder {
471  _IndexFinder(Finder parent, this.index) : super(parent);
472
473  final int index;
474
475  @override
476  String get description => '${parent.description} (ignoring all but index $index)';
477
478  @override
479  Iterable<Element> filter(Iterable<Element> parentCandidates) sync* {
480    yield parentCandidates.elementAt(index);
481  }
482}
483
484class _HitTestableFinder extends ChainedFinder {
485  _HitTestableFinder(Finder parent, this.alignment) : super(parent);
486
487  final Alignment alignment;
488
489  @override
490  String get description => '${parent.description} (considering only hit-testable ones)';
491
492  @override
493  Iterable<Element> filter(Iterable<Element> parentCandidates) sync* {
494    for (final Element candidate in parentCandidates) {
495      final RenderBox box = candidate.renderObject;
496      assert(box != null);
497      final Offset absoluteOffset = box.localToGlobal(alignment.alongSize(box.size));
498      final HitTestResult hitResult = HitTestResult();
499      WidgetsBinding.instance.hitTest(hitResult, absoluteOffset);
500      for (final HitTestEntry entry in hitResult.path) {
501        if (entry.target == candidate.renderObject) {
502          yield candidate;
503          break;
504        }
505      }
506    }
507  }
508}
509
510/// Searches a widget tree and returns nodes that match a particular
511/// pattern.
512abstract class MatchFinder extends Finder {
513  /// Initializes a predicate-based Finder. Used by subclasses to initialize the
514  /// [skipOffstage] property.
515  MatchFinder({ bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
516
517  /// Returns true if the given element matches the pattern.
518  ///
519  /// When implementing your own MatchFinder, this is the main method to override.
520  bool matches(Element candidate);
521
522  @override
523  Iterable<Element> apply(Iterable<Element> candidates) {
524    return candidates.where(matches);
525  }
526}
527
528class _TextFinder extends MatchFinder {
529  _TextFinder(this.text, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
530
531  final String text;
532
533  @override
534  String get description => 'text "$text"';
535
536  @override
537  bool matches(Element candidate) {
538    if (candidate.widget is Text) {
539      final Text textWidget = candidate.widget;
540      if (textWidget.data != null)
541        return textWidget.data == text;
542      return textWidget.textSpan.toPlainText() == text;
543    } else if (candidate.widget is EditableText) {
544      final EditableText editable = candidate.widget;
545      return editable.controller.text == text;
546    }
547    return false;
548  }
549}
550
551class _KeyFinder extends MatchFinder {
552  _KeyFinder(this.key, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
553
554  final Key key;
555
556  @override
557  String get description => 'key $key';
558
559  @override
560  bool matches(Element candidate) {
561    return candidate.widget.key == key;
562  }
563}
564
565class _WidgetTypeFinder extends MatchFinder {
566  _WidgetTypeFinder(this.widgetType, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
567
568  final Type widgetType;
569
570  @override
571  String get description => 'type "$widgetType"';
572
573  @override
574  bool matches(Element candidate) {
575    return candidate.widget.runtimeType == widgetType;
576  }
577}
578
579class _WidgetIconFinder extends MatchFinder {
580  _WidgetIconFinder(this.icon, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
581
582  final IconData icon;
583
584  @override
585  String get description => 'icon "$icon"';
586
587  @override
588  bool matches(Element candidate) {
589    final Widget widget = candidate.widget;
590    return widget is Icon && widget.icon == icon;
591  }
592}
593
594class _ElementTypeFinder extends MatchFinder {
595  _ElementTypeFinder(this.elementType, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
596
597  final Type elementType;
598
599  @override
600  String get description => 'type "$elementType"';
601
602  @override
603  bool matches(Element candidate) {
604    return candidate.runtimeType == elementType;
605  }
606}
607
608class _WidgetFinder extends MatchFinder {
609  _WidgetFinder(this.widget, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
610
611  final Widget widget;
612
613  @override
614  String get description => 'the given widget ($widget)';
615
616  @override
617  bool matches(Element candidate) {
618    return candidate.widget == widget;
619  }
620}
621
622class _WidgetPredicateFinder extends MatchFinder {
623  _WidgetPredicateFinder(this.predicate, { String description, bool skipOffstage = true })
624    : _description = description,
625      super(skipOffstage: skipOffstage);
626
627  final WidgetPredicate predicate;
628  final String _description;
629
630  @override
631  String get description => _description ?? 'widget matching predicate ($predicate)';
632
633  @override
634  bool matches(Element candidate) {
635    return predicate(candidate.widget);
636  }
637}
638
639class _ElementPredicateFinder extends MatchFinder {
640  _ElementPredicateFinder(this.predicate, { String description, bool skipOffstage = true })
641    : _description = description,
642      super(skipOffstage: skipOffstage);
643
644  final ElementPredicate predicate;
645  final String _description;
646
647  @override
648  String get description => _description ?? 'element matching predicate ($predicate)';
649
650  @override
651  bool matches(Element candidate) {
652    return predicate(candidate);
653  }
654}
655
656class _DescendantFinder extends Finder {
657  _DescendantFinder(
658    this.ancestor,
659    this.descendant, {
660    this.matchRoot = false,
661    bool skipOffstage = true,
662  }) : super(skipOffstage: skipOffstage);
663
664  final Finder ancestor;
665  final Finder descendant;
666  final bool matchRoot;
667
668  @override
669  String get description {
670    if (matchRoot)
671      return '${descendant.description} in the subtree(s) beginning with ${ancestor.description}';
672    return '${descendant.description} that has ancestor(s) with ${ancestor.description}';
673  }
674
675  @override
676  Iterable<Element> apply(Iterable<Element> candidates) {
677    return candidates.where((Element element) => descendant.evaluate().contains(element));
678  }
679
680  @override
681  Iterable<Element> get allCandidates {
682    final Iterable<Element> ancestorElements = ancestor.evaluate();
683    final List<Element> candidates = ancestorElements.expand<Element>(
684      (Element element) => collectAllElementsFrom(element, skipOffstage: skipOffstage)
685    ).toSet().toList();
686    if (matchRoot)
687      candidates.insertAll(0, ancestorElements);
688    return candidates;
689  }
690}
691
692class _AncestorFinder extends Finder {
693  _AncestorFinder(this.descendant, this.ancestor, { this.matchRoot = false }) : super(skipOffstage: false);
694
695  final Finder ancestor;
696  final Finder descendant;
697  final bool matchRoot;
698
699  @override
700  String get description {
701    if (matchRoot)
702      return 'ancestor ${ancestor.description} beginning with ${descendant.description}';
703    return '${ancestor.description} which is an ancestor of ${descendant.description}';
704  }
705
706  @override
707  Iterable<Element> apply(Iterable<Element> candidates) {
708    return candidates.where((Element element) => ancestor.evaluate().contains(element));
709  }
710
711  @override
712  Iterable<Element> get allCandidates {
713    final List<Element> candidates = <Element>[];
714    for (Element root in descendant.evaluate()) {
715      final List<Element> ancestors = <Element>[];
716      if (matchRoot)
717        ancestors.add(root);
718      root.visitAncestorElements((Element element) {
719        ancestors.add(element);
720        return true;
721      });
722      candidates.addAll(ancestors);
723    }
724    return candidates;
725  }
726}
727