diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 3da10ebfe6..9ba5f76047 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -2465,8 +2465,11 @@ class _OffstageElement extends SingleChildRenderObjectElement { @override void debugVisitOnstageChildren(ElementVisitor visitor) { - if (!widget.offstage) - super.debugVisitOnstageChildren(visitor); + assert(() { + if (!widget.offstage) + super.debugVisitOnstageChildren(visitor); + return true; + }()); } } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index e388b30a79..e0fd2595af 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -2666,6 +2666,11 @@ abstract class Element extends DiagnosticableTree implements BuildContext { /// The default implementation defers to [visitChildren] and therefore treats /// the element as onstage. /// + /// This method should only be used in tests and debug code. In production, + /// the vague concept of "onstage" vs "offstage" is meaningless and should not + /// be used to affect widget behavior; this method does nothing in release + /// builds. + /// /// See also: /// /// * [Offstage] widget that hides its children. @@ -2673,7 +2678,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext { /// * [RenderObject.visitChildrenForSemantics], in contrast to this method, /// designed specifically for excluding parts of the UI from the semantics /// tree. - void debugVisitOnstageChildren(ElementVisitor visitor) => visitChildren(visitor); + void debugVisitOnstageChildren(ElementVisitor visitor) { + assert(() { visitChildren(visitor); return true; }()); + } /// Wrapper around [visitChildren] for [BuildContext]. @override diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 479df85003..b6945e1fee 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -472,8 +472,11 @@ class _TheatreElement extends RenderObjectElement { @override void debugVisitOnstageChildren(ElementVisitor visitor) { - if (_onstage != null) - visitor(_onstage); + assert(() { + if (_onstage != null) + visitor(_onstage); + return true; + }()); } @override diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index 4f51d41818..414d098f65 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -1179,21 +1179,24 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render @override void debugVisitOnstageChildren(ElementVisitor visitor) { - _childElements.values.where((Element child) { - final SliverMultiBoxAdaptorParentData parentData = child.renderObject.parentData; - double itemExtent; - switch (renderObject.constraints.axis) { - case Axis.horizontal: - itemExtent = child.renderObject.paintBounds.width; - break; - case Axis.vertical: - itemExtent = child.renderObject.paintBounds.height; - break; - } - - return parentData.layoutOffset < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent && - parentData.layoutOffset + itemExtent > renderObject.constraints.scrollOffset; - }).forEach(visitor); + assert(() { + _childElements.values.where((Element child) { + final SliverMultiBoxAdaptorParentData parentData = child.renderObject.parentData; + double itemExtent; + switch (renderObject.constraints.axis) { + case Axis.horizontal: + itemExtent = child.renderObject.paintBounds.width; + break; + case Axis.vertical: + itemExtent = child.renderObject.paintBounds.height; + break; + } + final SliverConstraints constraints = renderObject.constraints; + return parentData.layoutOffset < constraints.scrollOffset + constraints.remainingPaintExtent + && parentData.layoutOffset + itemExtent > constraints.scrollOffset; + }).forEach(visitor); + return true; + }()); } } diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart index c348053f18..640e342b21 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -208,10 +208,13 @@ class _ViewportElement extends MultiChildRenderObjectElement { @override void debugVisitOnstageChildren(ElementVisitor visitor) { - children.where((Element e) { - final RenderSliver renderSliver = e.renderObject; - return renderSliver.geometry.visible; - }).forEach(visitor); + assert(() { + children.where((Element e) { + final RenderSliver renderSliver = e.renderObject; + return renderSliver.geometry.visible; + }).forEach(visitor); + return true; + }()); } } diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index a2fda53ded..fe14dd8c48 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -32,8 +32,7 @@ class CommonFinders { /// expect(find.text('Back'), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder text(String text, { bool skipOffstage = true }) => _TextFinder(text, skipOffstage: skipOffstage); /// Looks for widgets that contain a [Text] descendant with `text` @@ -51,8 +50,7 @@ class CommonFinders { /// tester.tap(find.widgetWithText(Button, 'Update')); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder widgetWithText(Type widgetType, String text, { bool skipOffstage = true }) { return find.ancestor( of: find.text(text, skipOffstage: skipOffstage), @@ -68,8 +66,7 @@ class CommonFinders { /// expect(find.byKey(backKey), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byKey(Key key, { bool skipOffstage = true }) => _KeyFinder(key, skipOffstage: skipOffstage); /// Finds widgets by searching for widgets with a particular type. @@ -86,8 +83,7 @@ class CommonFinders { /// expect(find.byType(IconButton), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byType(Type type, { bool skipOffstage = true }) => _WidgetTypeFinder(type, skipOffstage: skipOffstage); /// Finds [Icon] widgets containing icon data equal to the `icon` @@ -99,8 +95,7 @@ class CommonFinders { /// expect(find.byIcon(Icons.inbox), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byIcon(IconData icon, { bool skipOffstage = true }) => _WidgetIconFinder(icon, skipOffstage: skipOffstage); /// Looks for widgets that contain an [Icon] descendant displaying [IconData] @@ -118,8 +113,7 @@ class CommonFinders { /// tester.tap(find.widgetWithIcon(Button, Icons.arrow_forward)); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder widgetWithIcon(Type widgetType, IconData icon, { bool skipOffstage = true }) { return find.ancestor( of: find.byIcon(icon), @@ -141,8 +135,7 @@ class CommonFinders { /// expect(find.byElementType(SingleChildRenderObjectElement), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byElementType(Type type, { bool skipOffstage = true }) => _ElementTypeFinder(type, skipOffstage: skipOffstage); /// Finds widgets whose current widget is the instance given by the @@ -160,8 +153,7 @@ class CommonFinders { /// tester.tap(find.byWidget(myButton)); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byWidget(Widget widget, { bool skipOffstage = true }) => _WidgetFinder(widget, skipOffstage: skipOffstage); /// Finds widgets using a widget [predicate]. @@ -180,8 +172,7 @@ class CommonFinders { /// fails to locate the desired widget. Otherwise, the description prints the /// signature of the predicate function. /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byWidgetPredicate(WidgetPredicate predicate, { String description, bool skipOffstage = true }) { return _WidgetPredicateFinder(predicate, description: description, skipOffstage: skipOffstage); } @@ -194,8 +185,7 @@ class CommonFinders { /// expect(find.byTooltip('Back'), findsOneWidget); /// ``` /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byTooltip(String message, { bool skipOffstage = true }) { return byWidgetPredicate( (Widget widget) => widget is Tooltip && widget.message == message, @@ -222,8 +212,7 @@ class CommonFinders { /// fails to locate the desired widget. Otherwise, the description prints the /// signature of the predicate function. /// - /// If the `skipOffstage` argument is true (the default), then this skips - /// nodes that are [Offstage] or that are from inactive [Route]s. + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). Finder byElementPredicate(ElementPredicate predicate, { String description, bool skipOffstage = true }) { return _ElementPredicateFinder(predicate, description: description, skipOffstage: skipOffstage); } @@ -242,9 +231,13 @@ class CommonFinders { /// If the [matchRoot] argument is true then the widget(s) specified by [of] /// will be matched along with the descendants. /// - /// If the [skipOffstage] argument is true (the default), then nodes that are - /// [Offstage] or that are from inactive [Route]s are skipped. - Finder descendant({ Finder of, Finder matching, bool matchRoot = false, bool skipOffstage = true }) { + /// The `skipOffstage` argument maps to [Finder.skipOffstage] (q.v.). + Finder descendant({ + @required Finder of, + @required Finder matching, + bool matchRoot = false, + bool skipOffstage = true, + }) { return _DescendantFinder(of, matching, matchRoot: matchRoot, skipOffstage: skipOffstage); } @@ -296,9 +289,25 @@ abstract class Finder { /// Whether this finder skips nodes that are offstage. /// - /// If this is true, then the elements are walked using - /// [Element.debugVisitOnstageChildren]. This skips offstage children of - /// [Offstage] widgets, as well as children of inactive [Route]s. + /// By default, finders skip nodes that are considered "offstage". The term is + /// very loosely defined and is only meaningful in a debug environment; it is + /// not a term that applies in production. A widget is considered "offstage" + /// if it is "not really visible". + /// + /// If [skipOffstage] is true, then the elements are walked using + /// [Element.debugVisitOnstageChildren]. This skips hidden children of + /// [Offstage] widgets, children of inactive [Route]s, some non-visible + /// children in viewports, and other nodes that are generally considered to be + /// "not there" when considering what the user can see. + /// + /// The [skipOffstage] argument can be set to `false` to match _all_ nodes + /// regardless of this status. This is useful to test for the presence of + /// widgets being kept alive using [KeepAlive], for instance. + /// + /// See also: + /// + /// * [Element.debugVisitOnstageChildren], which can be overriden to + /// decide whether a child is onstage or offstage. final bool skipOffstage; /// Returns all the [Element]s that will be considered by this finder.