Unverified Commit 7984f6e0 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Implicit a11y scrolling for iOS (and caching in Viewports) (#17021)

parent be09a200
...@@ -2106,6 +2106,27 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2106,6 +2106,27 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// that are not physically visible. /// that are not physically visible.
Rect describeApproximatePaintClip(covariant RenderObject child) => null; Rect describeApproximatePaintClip(covariant RenderObject child) => null;
/// Returns a rect in this object's coordinate system that describes
/// which [SemanticsNode]s produced by the `child` should be included in the
/// semantics tree. [SemanticsNode]s from the `child` that are positioned
/// outside of this rect will be dropped. Child [SemanticsNode]s that are
/// positioned inside this rect, but outside of [describeApproximatePaintClip]
/// will be included in the tree marked as hidden. Child [SemanticsNode]s
/// that are inside of both rect will be included in the tree as regular
/// nodes.
///
/// This method only returns a non-null value if the semantics clip rect
/// is different from the rect returned by [describeApproximatePaintClip].
/// If the semantics clip rect and the paint clip rect are the same, this
/// method returns null.
///
/// A viewport would typically implement this method to include semantic nodes
/// in the semantics tree that are currently hidden just before the leading
/// or just after the trailing edge. These nodes have to be included in the
/// semantics tree to implement implicit accessibility scrolling on iOS where
/// the viewport scrolls implicitly when moving the accessibility focus from
/// a the last visible node in the viewport to the first hidden one.
Rect describeSemanticsClip(covariant RenderObject child) => null;
// SEMANTICS // SEMANTICS
...@@ -2279,7 +2300,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2279,7 +2300,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
); );
assert(fragment is _InterestingSemanticsFragment); assert(fragment is _InterestingSemanticsFragment);
final _InterestingSemanticsFragment interestingFragment = fragment; final _InterestingSemanticsFragment interestingFragment = fragment;
final SemanticsNode node = interestingFragment.compileChildren(_semantics?.parentClipRect).single; final SemanticsNode node = interestingFragment.compileChildren(
parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
parentPaintClipRect: _semantics?.parentPaintClipRect,
).single;
// Fragment only wants to add this node's SemanticsNode to the parent. // Fragment only wants to add this node's SemanticsNode to the parent.
assert(interestingFragment.config == null && node == _semantics); assert(interestingFragment.config == null && node == _semantics);
} }
...@@ -3025,7 +3049,10 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { ...@@ -3025,7 +3049,10 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
final List<RenderObject> _ancestorChain; final List<RenderObject> _ancestorChain;
/// The children to be added to the parent. /// The children to be added to the parent.
Iterable<SemanticsNode> compileChildren(Rect parentClipRect); Iterable<SemanticsNode> compileChildren({
@required Rect parentSemanticsClipRect,
@required Rect parentPaintClipRect
});
/// The [SemanticsConfiguration] the child wants to merge into the parent's /// The [SemanticsConfiguration] the child wants to merge into the parent's
/// [SemanticsNode] or null if it doesn't want to merge anything. /// [SemanticsNode] or null if it doesn't want to merge anything.
...@@ -3093,9 +3120,10 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3093,9 +3120,10 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
}) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings); }) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
@override @override
Iterable<SemanticsNode> compileChildren(Rect parentClipRect) sync* { Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
assert(_tagsForChildren == null || _tagsForChildren.isEmpty); assert(_tagsForChildren == null || _tagsForChildren.isEmpty);
assert(parentClipRect == null); assert(parentSemanticsClipRect == null);
assert(parentPaintClipRect == null);
assert(_ancestorChain.length == 1); assert(_ancestorChain.length == 1);
owner._semantics ??= new SemanticsNode.root( owner._semantics ??= new SemanticsNode.root(
...@@ -3104,14 +3132,18 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3104,14 +3132,18 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
); );
final SemanticsNode node = owner._semantics; final SemanticsNode node = owner._semantics;
assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity())); assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
assert(node.parentClipRect == null); assert(node.parentSemanticsClipRect == null);
assert(node.parentPaintClipRect == null);
node.rect = owner.semanticBounds; node.rect = owner.semanticBounds;
final List<SemanticsNode> children = <SemanticsNode>[]; final List<SemanticsNode> children = <SemanticsNode>[];
for (_InterestingSemanticsFragment fragment in _children) { for (_InterestingSemanticsFragment fragment in _children) {
assert(fragment.config == null); assert(fragment.config == null);
children.addAll(fragment.compileChildren(parentClipRect)); children.addAll(fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect,
parentPaintClipRect: parentPaintClipRect,
));
} }
node.updateWith(config: null, childrenInInversePaintOrder: children); node.updateWith(config: null, childrenInInversePaintOrder: children);
...@@ -3171,22 +3203,22 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3171,22 +3203,22 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[]; final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
@override @override
Iterable<SemanticsNode> compileChildren(Rect parentClipRect) sync* { Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
if (!_isExplicit) { if (!_isExplicit) {
owner._semantics = null; owner._semantics = null;
for (_InterestingSemanticsFragment fragment in _children) { for (_InterestingSemanticsFragment fragment in _children) {
assert(_ancestorChain.first == fragment._ancestorChain.last); assert(_ancestorChain.first == fragment._ancestorChain.last);
fragment._ancestorChain.addAll(_ancestorChain.sublist(1)); fragment._ancestorChain.addAll(_ancestorChain.sublist(1));
yield* fragment.compileChildren(parentClipRect); yield* fragment.compileChildren(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect);
} }
return; return;
} }
final _SemanticsGeometry geometry = _needsGeometryUpdate final _SemanticsGeometry geometry = _needsGeometryUpdate
? new _SemanticsGeometry(parentClipRect: parentClipRect, ancestors: _ancestorChain) ? new _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
: null; : null;
if (!_mergeIntoParent && (geometry?.isInvisible == true)) if (!_mergeIntoParent && (geometry?.dropFromTree == true))
return; // Drop the node, it's not going to be visible. return; // Drop the node, it's not going to be visible.
owner._semantics ??= new SemanticsNode(showOnScreen: owner.showOnScreen); owner._semantics ??= new SemanticsNode(showOnScreen: owner.showOnScreen);
...@@ -3199,12 +3231,17 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3199,12 +3231,17 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
node node
..rect = geometry.rect ..rect = geometry.rect
..transform = geometry.transform ..transform = geometry.transform
..parentClipRect = geometry.clipRect; ..parentSemanticsClipRect = geometry.semanticsClipRect
..parentPaintClipRect = geometry.paintClipRect;
if (!_mergeIntoParent && geometry.markAsHidden) {
_ensureConfigIsWritable();
_config.isHidden = true;
}
} }
final List<SemanticsNode> children = <SemanticsNode>[]; final List<SemanticsNode> children = <SemanticsNode>[];
for (_InterestingSemanticsFragment fragment in _children) for (_InterestingSemanticsFragment fragment in _children)
children.addAll(fragment.compileChildren(node.parentClipRect)); children.addAll(fragment.compileChildren(parentSemanticsClipRect: node.parentSemanticsClipRect, parentPaintClipRect: node.parentPaintClipRect));
if (_config.isSemanticBoundary) { if (_config.isSemanticBoundary) {
owner.assembleSemanticsNode(node, _config, children); owner.assembleSemanticsNode(node, _config, children);
...@@ -3226,14 +3263,18 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3226,14 +3263,18 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
_children.add(fragment); _children.add(fragment);
if (fragment.config == null) if (fragment.config == null)
continue; continue;
if (!_isConfigWritable) { _ensureConfigIsWritable();
_config = _config.copy();
_isConfigWritable = true;
}
_config.absorb(fragment.config); _config.absorb(fragment.config);
} }
} }
void _ensureConfigIsWritable() {
if (!_isConfigWritable) {
_config = _config.copy();
_isConfigWritable = true;
}
}
bool _isExplicit = false; bool _isExplicit = false;
@override @override
...@@ -3257,61 +3298,97 @@ class _SemanticsGeometry { ...@@ -3257,61 +3298,97 @@ class _SemanticsGeometry {
/// (first [RenderObject] in the list) and its closest ancestor [RenderObject] /// (first [RenderObject] in the list) and its closest ancestor [RenderObject]
/// that also owns its own [SemanticsNode] (last [RenderObject] in the list). /// that also owns its own [SemanticsNode] (last [RenderObject] in the list).
_SemanticsGeometry({ _SemanticsGeometry({
@required Rect parentClipRect, @required Rect parentSemanticsClipRect,
@required Rect parentPaintClipRect,
@required List<RenderObject> ancestors, @required List<RenderObject> ancestors,
}) { }) {
_computeValues(parentClipRect, ancestors); _computeValues(parentSemanticsClipRect, parentPaintClipRect, ancestors);
} }
Rect _clipRect; Rect _paintClipRect;
Rect _semanticsClipRect;
Matrix4 _transform; Matrix4 _transform;
Rect _rect; Rect _rect;
/// Value for [SemanticsNode.transform]. /// Value for [SemanticsNode.transform].
Matrix4 get transform => _transform; Matrix4 get transform => _transform;
/// Value for [SemanticsNode.parentClipRect]. /// Value for [SemanticsNode.parentSemanticsClipRect].
Rect get clipRect => _clipRect; Rect get semanticsClipRect => _semanticsClipRect;
/// Value for [SemanticsNode.parentPaintClipRect].
Rect get paintClipRect => _paintClipRect;
/// Value for [SemanticsNode.rect]. /// Value for [SemanticsNode.rect].
Rect get rect => _rect; Rect get rect => _rect;
void _computeValues(Rect parentClipRect, List<RenderObject> ancestors) { void _computeValues(Rect parentSemanticsClipRect, Rect parentPaintClipRect, List<RenderObject> ancestors) {
assert(ancestors.length > 1); assert(ancestors.length > 1);
_transform = new Matrix4.identity(); _transform = new Matrix4.identity();
_clipRect = parentClipRect; _semanticsClipRect = parentSemanticsClipRect;
_paintClipRect = parentPaintClipRect;
for (int index = ancestors.length-1; index > 0; index -= 1) { for (int index = ancestors.length-1; index > 0; index -= 1) {
final RenderObject parent = ancestors[index]; final RenderObject parent = ancestors[index];
final RenderObject child = ancestors[index-1]; final RenderObject child = ancestors[index-1];
_clipRect = _intersectClipRect(parent.describeApproximatePaintClip(child)); final Rect parentSemanticsClipRect = parent.describeSemanticsClip(child);
if (_clipRect != null) { if (parentSemanticsClipRect != null) {
if (_clipRect.isEmpty) { _semanticsClipRect = parentSemanticsClipRect;
_clipRect = Rect.zero; _paintClipRect = _intersectRects(_paintClipRect, parent.describeApproximatePaintClip(child));
} else { } else {
final Matrix4 clipTransform = new Matrix4.identity(); _semanticsClipRect = _intersectRects(_semanticsClipRect, parent.describeApproximatePaintClip(child));
parent.applyPaintTransform(child, clipTransform);
_clipRect = MatrixUtils.inverseTransformRect(clipTransform, _clipRect);
}
} }
_semanticsClipRect = _transformRect(_semanticsClipRect, parent, child);
_paintClipRect = _transformRect(_paintClipRect, parent, child);
parent.applyPaintTransform(child, _transform); parent.applyPaintTransform(child, _transform);
} }
final RenderObject owner = ancestors.first; final RenderObject owner = ancestors.first;
_rect = _clipRect == null ? owner.semanticBounds : _clipRect.intersect(owner.semanticBounds); _rect = _semanticsClipRect == null ? owner.semanticBounds : _semanticsClipRect.intersect(owner.semanticBounds);
if (_paintClipRect != null) {
final Rect paintRect = _paintClipRect.intersect(_rect);
_markAsHidden = paintRect.isEmpty && !_rect.isEmpty;
if (!_markAsHidden)
_rect = paintRect;
}
}
/// From parent to child coordinate system.
static Rect _transformRect(Rect rect, RenderObject parent, RenderObject child) {
if (rect == null)
return null;
if (rect.isEmpty)
return Rect.zero;
final Matrix4 transform = new Matrix4.identity();
parent.applyPaintTransform(child, transform);
return MatrixUtils.inverseTransformRect(transform, rect);
} }
Rect _intersectClipRect(Rect other) { static Rect _intersectRects(Rect a, Rect b) {
if (_clipRect == null) if (a == null)
return other; return b;
if (other == null) if (b == null)
return _clipRect; return a;
return _clipRect.intersect(other); return a.intersect(b);
} }
/// Whether a [SemanticsNode] annotated with the geometric information tracked /// Whether the [SemanticsNode] annotated with the geometric information tracked
/// by this object would be visible on screen. /// by this object can be dropped from the semantics tree without losing
bool get isInvisible { /// semantics information.
bool get dropFromTree {
return _rect.isEmpty; return _rect.isEmpty;
} }
/// Whether the [SemanticsNode] annotated with the geometric information
/// tracked by this object should be marked as hidden because it is not
/// visible on screen.
///
/// Hidden elements should still be included in the tree to work around
/// platform limitations (e.g. accessibility scrolling on iOS).
///
/// See also:
///
/// * [SemanticsFlag.isHidden] for the purpose of marking a node as hidden.
bool get markAsHidden => _markAsHidden;
bool _markAsHidden = false;
} }
...@@ -104,6 +104,8 @@ class SliverConstraints extends Constraints { ...@@ -104,6 +104,8 @@ class SliverConstraints extends Constraints {
@required this.crossAxisExtent, @required this.crossAxisExtent,
@required this.crossAxisDirection, @required this.crossAxisDirection,
@required this.viewportMainAxisExtent, @required this.viewportMainAxisExtent,
@required this.remainingCacheExtent,
@required this.cacheOrigin,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(growthDirection != null), assert(growthDirection != null),
assert(userScrollDirection != null), assert(userScrollDirection != null),
...@@ -112,7 +114,9 @@ class SliverConstraints extends Constraints { ...@@ -112,7 +114,9 @@ class SliverConstraints extends Constraints {
assert(remainingPaintExtent != null), assert(remainingPaintExtent != null),
assert(crossAxisExtent != null), assert(crossAxisExtent != null),
assert(crossAxisDirection != null), assert(crossAxisDirection != null),
assert(viewportMainAxisExtent != null); assert(viewportMainAxisExtent != null),
assert(remainingCacheExtent != null),
assert(cacheOrigin != null);
/// Creates a copy of this object but with the given fields replaced with the /// Creates a copy of this object but with the given fields replaced with the
/// new values. /// new values.
...@@ -126,6 +130,8 @@ class SliverConstraints extends Constraints { ...@@ -126,6 +130,8 @@ class SliverConstraints extends Constraints {
double crossAxisExtent, double crossAxisExtent,
AxisDirection crossAxisDirection, AxisDirection crossAxisDirection,
double viewportMainAxisExtent, double viewportMainAxisExtent,
double remainingCacheExtent,
double cacheOrigin,
}) { }) {
return new SliverConstraints( return new SliverConstraints(
axisDirection: axisDirection ?? this.axisDirection, axisDirection: axisDirection ?? this.axisDirection,
...@@ -137,6 +143,8 @@ class SliverConstraints extends Constraints { ...@@ -137,6 +143,8 @@ class SliverConstraints extends Constraints {
crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent, crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent,
crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection, crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection,
viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent, viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent,
remainingCacheExtent: remainingCacheExtent ?? this.remainingCacheExtent,
cacheOrigin: cacheOrigin ?? this.cacheOrigin,
); );
} }
...@@ -258,6 +266,47 @@ class SliverConstraints extends Constraints { ...@@ -258,6 +266,47 @@ class SliverConstraints extends Constraints {
/// For a vertical list, this is the height of the viewport. /// For a vertical list, this is the height of the viewport.
final double viewportMainAxisExtent; final double viewportMainAxisExtent;
/// Where the cache area starts relative to the [scrollOffset].
///
/// Slivers that fall into the cache area located before the leading edge and
/// after the trailing edge of the viewport should still render content
/// because they are about to become visible when the user scrolls.
///
/// The [cacheOrigin] describes where the [remainingCacheExtent] starts relative
/// to the [scrollOffset]. A cache origin of 0 means that the sliver does not
/// have to provide any content before the current [scrollOffset]. A
/// [cacheOrigin] of -250.0 means that even though the first visible part of
/// the sliver will be at the provided [scrollOffset], the sliver should
/// render content starting 250.0 before the [scrollOffset] to fill the
/// cache area of the viewport.
///
/// The [cacheOrigin] is always negative or zero and will never exceed
/// -[scrollOffset]. In other words, a sliver is never asked to provide
/// content before its zero [scrollOffset].
///
/// See also:
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double cacheOrigin;
/// Describes how much content the sliver should provide starting from the
/// [cacheOrigin].
///
/// Not all content in the [remainingCacheExtent] will be visible as some
/// of it might fall into the cache area of the viewport.
///
/// Each sliver should start laying out content at the [cacheOrigin] and
/// try to provide as much content as the [remainingCacheExtent] allows.
///
/// The [remainingCacheExtent] is always larger or equal to the
/// [remainingPaintExtent]. Content, that falls in the [remainingCacheExtent],
/// but is outside of the [remainingPaintExtent] is currently not visible
/// in the viewport.
///
/// See also:
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double remainingCacheExtent;
/// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured. /// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured.
Axis get axis => axisDirectionToAxis(axisDirection); Axis get axis => axisDirectionToAxis(axisDirection);
...@@ -361,6 +410,8 @@ class SliverConstraints extends Constraints { ...@@ -361,6 +410,8 @@ class SliverConstraints extends Constraints {
verify(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), 'The "axisDirection" and the "crossAxisDirection" are along the same axis.'); verify(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), 'The "axisDirection" and the "crossAxisDirection" are along the same axis.');
verify(viewportMainAxisExtent >= 0.0, 'The "viewportMainAxisExtent" is negative.'); verify(viewportMainAxisExtent >= 0.0, 'The "viewportMainAxisExtent" is negative.');
verify(remainingPaintExtent >= 0.0, 'The "remainingPaintExtent" is negative.'); verify(remainingPaintExtent >= 0.0, 'The "remainingPaintExtent" is negative.');
verify(remainingCacheExtent >= 0.0, 'The "remainingCacheExtent" is negative.');
verify(cacheOrigin <= 0.0, 'The "cacheOrigin" is positive.');
verify(isNormalized, 'The constraints are not normalized.'); // should be redundant with earlier checks verify(isNormalized, 'The constraints are not normalized.'); // should be redundant with earlier checks
return true; return true;
}()); }());
...@@ -382,7 +433,9 @@ class SliverConstraints extends Constraints { ...@@ -382,7 +433,9 @@ class SliverConstraints extends Constraints {
&& typedOther.remainingPaintExtent == remainingPaintExtent && typedOther.remainingPaintExtent == remainingPaintExtent
&& typedOther.crossAxisExtent == crossAxisExtent && typedOther.crossAxisExtent == crossAxisExtent
&& typedOther.crossAxisDirection == crossAxisDirection && typedOther.crossAxisDirection == crossAxisDirection
&& typedOther.viewportMainAxisExtent == viewportMainAxisExtent; && typedOther.viewportMainAxisExtent == viewportMainAxisExtent
&& typedOther.remainingCacheExtent == remainingCacheExtent
&& typedOther.cacheOrigin == cacheOrigin;
} }
@override @override
...@@ -396,6 +449,8 @@ class SliverConstraints extends Constraints { ...@@ -396,6 +449,8 @@ class SliverConstraints extends Constraints {
crossAxisExtent, crossAxisExtent,
crossAxisDirection, crossAxisDirection,
viewportMainAxisExtent, viewportMainAxisExtent,
remainingCacheExtent,
cacheOrigin,
); );
} }
...@@ -408,9 +463,11 @@ class SliverConstraints extends Constraints { ...@@ -408,9 +463,11 @@ class SliverConstraints extends Constraints {
'scrollOffset: ${scrollOffset.toStringAsFixed(1)}, ' 'scrollOffset: ${scrollOffset.toStringAsFixed(1)}, '
'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}, ' + 'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}, ' +
(overlap != 0.0 ? 'overlap: ${overlap.toStringAsFixed(1)}, ' : '') + (overlap != 0.0 ? 'overlap: ${overlap.toStringAsFixed(1)}, ' : '') +
'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}, ' + 'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}, '
'crossAxisDirection: $crossAxisDirection, ' + 'crossAxisDirection: $crossAxisDirection, '
'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}' + 'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}, '
'remainingCacheExtent: ${remainingCacheExtent.toStringAsFixed(1)} '
'cacheOrigin: ${cacheOrigin.toStringAsFixed(1)} '
')'; ')';
} }
} }
...@@ -440,6 +497,7 @@ class SliverGeometry extends Diagnosticable { ...@@ -440,6 +497,7 @@ class SliverGeometry extends Diagnosticable {
bool visible, bool visible,
this.hasVisualOverflow: false, this.hasVisualOverflow: false,
this.scrollOffsetCorrection, this.scrollOffsetCorrection,
double cacheExtent,
}) : assert(scrollExtent != null), }) : assert(scrollExtent != null),
assert(paintExtent != null), assert(paintExtent != null),
assert(paintOrigin != null), assert(paintOrigin != null),
...@@ -448,6 +506,7 @@ class SliverGeometry extends Diagnosticable { ...@@ -448,6 +506,7 @@ class SliverGeometry extends Diagnosticable {
assert(scrollOffsetCorrection != 0.0), assert(scrollOffsetCorrection != 0.0),
layoutExtent = layoutExtent ?? paintExtent, layoutExtent = layoutExtent ?? paintExtent,
hitTestExtent = hitTestExtent ?? paintExtent, hitTestExtent = hitTestExtent ?? paintExtent,
cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent,
visible = visible ?? paintExtent > 0.0; visible = visible ?? paintExtent > 0.0;
/// A sliver that occupies no space at all. /// A sliver that occupies no space at all.
...@@ -585,6 +644,18 @@ class SliverGeometry extends Diagnosticable { ...@@ -585,6 +644,18 @@ class SliverGeometry extends Diagnosticable {
/// its offset based on this value. /// its offset based on this value.
final double scrollOffsetCorrection; final double scrollOffsetCorrection;
/// How many pixels the sliver has consumed in the
/// [SliverConstraints.remainingCacheExtent].
///
/// This value should be equal to or larger than the [layoutExtent] because
/// the sliver allways consumes at least the [layoutExtent] from the
/// [SliverConstraints.remainingCacheExtent] and possibly more if it falls
/// into the cache area of the viewport.
///
/// See also:
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double cacheExtent;
/// Asserts that this geometry is internally consistent. /// Asserts that this geometry is internally consistent.
/// ///
/// Does nothing if asserts are disabled. Always returns true. /// Does nothing if asserts are disabled. Always returns true.
...@@ -607,6 +678,7 @@ class SliverGeometry extends Diagnosticable { ...@@ -607,6 +678,7 @@ class SliverGeometry extends Diagnosticable {
verify(paintOrigin != null, 'The "paintOrigin" is null.'); verify(paintOrigin != null, 'The "paintOrigin" is null.');
verify(layoutExtent != null, 'The "layoutExtent" is null.'); verify(layoutExtent != null, 'The "layoutExtent" is null.');
verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.'); verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.');
verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.');
if (layoutExtent > paintExtent) { if (layoutExtent > paintExtent) {
verify(false, verify(false,
'The "layoutExtent" exceeds the "paintExtent".\n' + 'The "layoutExtent" exceeds the "paintExtent".\n' +
...@@ -655,6 +727,7 @@ class SliverGeometry extends Diagnosticable { ...@@ -655,6 +727,7 @@ class SliverGeometry extends Diagnosticable {
properties.add(new DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent)); properties.add(new DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
properties.add(new DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false)); properties.add(new DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false));
properties.add(new DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null)); properties.add(new DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null));
properties.add(new DoubleProperty('cacheExtent', cacheExtent, defaultValue: 0.0));
} }
} }
...@@ -1123,6 +1196,22 @@ abstract class RenderSliver extends RenderObject { ...@@ -1123,6 +1196,22 @@ abstract class RenderSliver extends RenderObject {
return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent); return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent);
} }
/// Computes the portion of the region from `from` to `to` that is within
/// the cache extent of the viewport, assuming that only the region from the
/// [SliverConstraints.cacheOrigin] that is
/// [SliverConstraints.remainingCacheExtent] high is visible, and that
/// the relationship between scroll offsets and paint offsets is linear.
///
/// This method is not useful if there is not a 1:1 relationship between
/// consumed scroll offset and consumed cache extent.
double calculateCacheOffset(SliverConstraints constraints, { @required double from, @required double to }) {
assert(from <= to);
final double a = constraints.scrollOffset + constraints.cacheOrigin;
final double b = constraints.scrollOffset + constraints.remainingCacheExtent;
// the clamp on the next line is to avoid floating point rounding errors
return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingCacheExtent);
}
/// Returns the distance from the leading _visible_ edge of the sliver to the /// Returns the distance from the leading _visible_ edge of the sliver to the
/// side of the given child closest to that edge. /// side of the given child closest to that edge.
/// ///
...@@ -1537,11 +1626,14 @@ class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { ...@@ -1537,11 +1626,14 @@ class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
} }
assert(childExtent != null); assert(childExtent != null);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent); final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
assert(paintedChildSize.isFinite); assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0); assert(paintedChildSize >= 0.0);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: childExtent, scrollExtent: childExtent,
paintExtent: paintedChildSize, paintExtent: paintedChildSize,
cacheExtent: cacheExtent,
maxPaintExtent: childExtent, maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize, hitTestExtent: paintedChildSize,
hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0, hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
......
...@@ -140,11 +140,11 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -140,11 +140,11 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
final double itemExtent = this.itemExtent; final double itemExtent = this.itemExtent;
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints( final BoxConstraints childConstraints = constraints.asBoxConstraints(
minExtent: itemExtent, minExtent: itemExtent,
...@@ -242,9 +242,16 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -242,9 +242,16 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
to: trailingScrollOffset, to: trailingScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset, scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent, paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset, maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll. // Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex) hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex)
......
...@@ -513,11 +513,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -513,11 +513,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout(); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final SliverGridLayout layout = _gridDelegate.getLayout(constraints); final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
...@@ -617,11 +617,17 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -617,11 +617,17 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
from: leadingScrollOffset, from: leadingScrollOffset,
to: trailingScrollOffset, to: trailingScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent, scrollExtent: estimatedTotalExtent,
paintExtent: paintExtent, paintExtent: paintExtent,
maxPaintExtent: estimatedTotalExtent, maxPaintExtent: estimatedTotalExtent,
cacheExtent: cacheExtent,
// Conservative to avoid complexity. // Conservative to avoid complexity.
hasVisualOverflow: true, hasVisualOverflow: true,
); );
......
...@@ -47,11 +47,11 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -47,11 +47,11 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout(); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent; final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingPaintExtent >= 0.0); assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints(); final BoxConstraints childConstraints = constraints.asBoxConstraints();
int leadingGarbage = 0; int leadingGarbage = 0;
int trailingGarbage = 0; int trailingGarbage = 0;
...@@ -269,9 +269,15 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -269,9 +269,15 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
from: childScrollOffset(firstChild), from: childScrollOffset(firstChild),
to: endScrollOffset, to: endScrollOffset,
); );
final double cacheExtent = calculateCacheOffset(
constraints,
from: childScrollOffset(firstChild),
to: endScrollOffset,
);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset, scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent, paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset, maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll. // Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0, hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
......
...@@ -298,36 +298,6 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...@@ -298,36 +298,6 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
_keepAliveBucket.values.forEach(visitor); _keepAliveBucket.values.forEach(visitor);
} }
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
switch (constraints.normalizedGrowthDirection) {
case GrowthDirection.forward:
super.visitChildrenForSemantics((RenderObject child) {
// The sliver is overlapped at the leading edge; check if trailing edge is visible.
final Offset bottomRightInViewport = MatrixUtils.transformPoint(
child.getTransformTo(parent), child.semanticBounds.bottomRight
);
final double endOverlap = constraints.overlap;
if ((constraints.axis == Axis.vertical && bottomRightInViewport.dy > endOverlap) ||
(constraints.axis == Axis.horizontal && bottomRightInViewport.dx > endOverlap))
visitor(child);
});
break;
case GrowthDirection.reverse:
super.visitChildrenForSemantics((RenderObject child) {
// The sliver is overlapped at the trailing edge; check if leading edge is visible.
final Offset topLeftInViewport = MatrixUtils.transformPoint(
child.getTransformTo(parent), child.semanticBounds.topLeft
);
final double startOverlap = constraints.remainingPaintExtent - constraints.overlap;
if ((constraints.axis == Axis.vertical && topLeftInViewport.dy < startOverlap) ||
(constraints.axis == Axis.horizontal && topLeftInViewport.dx < startOverlap))
visitor(child);
});
break;
}
}
/// Called during layout to create and add the child with the given index and /// Called during layout to create and add the child with the given index and
/// scroll offset. /// scroll offset.
/// ///
......
...@@ -182,8 +182,10 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -182,8 +182,10 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
child.layout( child.layout(
constraints.copyWith( constraints.copyWith(
scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding), scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
overlap: 0.0, overlap: 0.0,
remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding), remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding), crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
), ),
parentUsesSize: true, parentUsesSize: true,
...@@ -206,6 +208,17 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -206,6 +208,17 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
to: mainAxisPadding + childLayoutGeometry.scrollExtent, to: mainAxisPadding + childLayoutGeometry.scrollExtent,
); );
final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent; final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
final double beforePaddingCacheExtent = calculateCacheOffset(
constraints,
from: 0.0,
to: beforePadding,
);
final double afterPaddingCacheExtent = calculateCacheOffset(
constraints,
from: beforePadding + childLayoutGeometry.scrollExtent,
to: mainAxisPadding + childLayoutGeometry.scrollExtent,
);
final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
final double paintExtent = math.min( final double paintExtent = math.min(
beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent), beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
constraints.remainingPaintExtent, constraints.remainingPaintExtent,
...@@ -214,6 +227,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -214,6 +227,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent, scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
paintExtent: paintExtent, paintExtent: paintExtent,
layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent), layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent, maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
hitTestExtent: math.max( hitTestExtent: math.max(
mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent, mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
......
...@@ -292,13 +292,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent ...@@ -292,13 +292,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent
final bool overlapsContent = constraints.overlap > 0.0; final bool overlapsContent = constraints.overlap > 0.0;
excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent); excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent);
layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent); layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
geometry = new SliverGeometry( geometry = new SliverGeometry(
scrollExtent: maxExtent, scrollExtent: maxExtent,
paintOrigin: constraints.overlap, paintOrigin: constraints.overlap,
paintExtent: math.min(childExtent, constraints.remainingPaintExtent), paintExtent: math.min(childExtent, constraints.remainingPaintExtent),
layoutExtent: (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent), layoutExtent: layoutExtent,
maxPaintExtent: maxExtent, maxPaintExtent: maxExtent,
maxScrollObstructionExtent: minExtent, maxScrollObstructionExtent: minExtent,
cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
); );
} }
......
...@@ -53,6 +53,14 @@ abstract class RenderAbstractViewport extends RenderObject { ...@@ -53,6 +53,14 @@ abstract class RenderAbstractViewport extends RenderObject {
/// descendant of the viewport and there must not be any other /// descendant of the viewport and there must not be any other
/// [RenderAbstractViewport] objects between the target and this object. /// [RenderAbstractViewport] objects between the target and this object.
double getOffsetToReveal(RenderObject target, double alignment); double getOffsetToReveal(RenderObject target, double alignment);
/// The default value for the cache extent of the viewport.
///
/// See also:
///
/// * [RenderViewportBase.cacheExtent] for a definition of the cache extent.
@protected
static const double defaultCacheExtent = 250.0;
} }
/// A base class for render objects that are bigger on the inside. /// A base class for render objects that are bigger on the inside.
...@@ -82,13 +90,15 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -82,13 +90,15 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
AxisDirection axisDirection: AxisDirection.down, AxisDirection axisDirection: AxisDirection.down,
@required AxisDirection crossAxisDirection, @required AxisDirection crossAxisDirection,
@required ViewportOffset offset, @required ViewportOffset offset,
double cacheExtent,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(crossAxisDirection != null), assert(crossAxisDirection != null),
assert(offset != null), assert(offset != null),
assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)), assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
_axisDirection = axisDirection, _axisDirection = axisDirection,
_crossAxisDirection = crossAxisDirection, _crossAxisDirection = crossAxisDirection,
_offset = offset; _offset = offset,
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent;
@override @override
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
...@@ -99,11 +109,9 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -99,11 +109,9 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
@override @override
void visitChildrenForSemantics(RenderObjectVisitor visitor) { void visitChildrenForSemantics(RenderObjectVisitor visitor) {
for (RenderSliver sliver in childrenInPaintOrder) { childrenInPaintOrder
if (sliver.geometry.paintExtent != 0 && .where((RenderSliver sliver) => sliver.geometry.visible || sliver.geometry.cacheExtent > 0.0)
sliver.constraints.overlap < sliver.geometry.paintOrigin + sliver.geometry.paintExtent) .forEach(visitor);
visitor(sliver);
}
} }
/// The direction in which the [SliverConstraints.scrollOffset] increases. /// The direction in which the [SliverConstraints.scrollOffset] increases.
...@@ -166,6 +174,35 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -166,6 +174,35 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
markNeedsLayout(); markNeedsLayout();
} }
/// {@template flutter.rendering.viewport.cacheExtent}
/// The viewport has an area before and after the visible area to cache items
/// that are about to become visible when the user scrolls.
///
/// Items that fall in this cache area are laid out even though they are not
/// (yet) visible on screen. The [cacheExtent] describes how many pixels
/// the cache area extends before the leading edge and after the trailing edge
/// of the viewport.
///
/// The total extent, which the viewport will try to cover with children, is
/// [cacheExtent] before the leading edge + extent of the main axis +
/// [cacheExtent] after the trailing edge.
///
/// The cache area is also used to implement implicit accessibility scrolling
/// on iOS: When the accessibility focus moves from an item in the visible
/// viewport to an invisible item in the cache area, the framework will bring
/// that item into view with an (implicit) scroll action.
/// {@endtemplate}
double get cacheExtent => _cacheExtent;
double _cacheExtent;
set cacheExtent(double value) {
value = value ?? RenderAbstractViewport.defaultCacheExtent;
assert(value != null);
if (value == _cacheExtent)
return;
_cacheExtent = value;
markNeedsLayout();
}
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
...@@ -262,17 +299,19 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -262,17 +299,19 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
/// encountered, if any. Otherwise returns 0.0. Typical callers will call this /// encountered, if any. Otherwise returns 0.0. Typical callers will call this
/// function repeatedly until it returns 0.0. /// function repeatedly until it returns 0.0.
@protected @protected
double layoutChildSequence( double layoutChildSequence({
RenderSliver child, @required RenderSliver child,
double scrollOffset, @required double scrollOffset,
double overlap, @required double overlap,
double layoutOffset, @required double layoutOffset,
double remainingPaintExtent, @required double remainingPaintExtent,
double mainAxisExtent, @required double mainAxisExtent,
double crossAxisExtent, @required double crossAxisExtent,
GrowthDirection growthDirection, @required GrowthDirection growthDirection,
RenderSliver advance(RenderSliver child), @required RenderSliver advance(RenderSliver child),
) { @required double remainingCacheExtent,
@required double cacheOrigin,
}) {
assert(scrollOffset.isFinite); assert(scrollOffset.isFinite);
assert(scrollOffset >= 0.0); assert(scrollOffset >= 0.0);
final double initialLayoutOffset = layoutOffset; final double initialLayoutOffset = layoutOffset;
...@@ -280,18 +319,32 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -280,18 +319,32 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection); applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection);
assert(adjustedUserScrollDirection != null); assert(adjustedUserScrollDirection != null);
double maxPaintOffset = layoutOffset + overlap; double maxPaintOffset = layoutOffset + overlap;
while (child != null) { while (child != null) {
assert(scrollOffset >= 0.0); final double sliverScrollOffset = scrollOffset <= 0.0 ? 0.0 : scrollOffset;
// If the scrollOffset is too small we adjust the paddedOrigin because it
// doesn't make sense to ask a sliver for content before its scroll
// offset.
final double corectedCacheOrigin = math.max(cacheOrigin, -sliverScrollOffset);
final double cacheExtentCorrection = cacheOrigin - corectedCacheOrigin;
assert(sliverScrollOffset >= corectedCacheOrigin.abs());
assert(corectedCacheOrigin <= 0.0);
assert(sliverScrollOffset >= 0.0);
assert(cacheExtentCorrection <= 0.0);
child.layout(new SliverConstraints( child.layout(new SliverConstraints(
axisDirection: axisDirection, axisDirection: axisDirection,
growthDirection: growthDirection, growthDirection: growthDirection,
userScrollDirection: adjustedUserScrollDirection, userScrollDirection: adjustedUserScrollDirection,
scrollOffset: scrollOffset, scrollOffset: sliverScrollOffset,
overlap: maxPaintOffset - layoutOffset, overlap: maxPaintOffset - layoutOffset,
remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset), remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset),
crossAxisExtent: crossAxisExtent, crossAxisExtent: crossAxisExtent,
crossAxisDirection: crossAxisDirection, crossAxisDirection: crossAxisDirection,
viewportMainAxisExtent: mainAxisExtent, viewportMainAxisExtent: mainAxisExtent,
remainingCacheExtent: math.max(0.0, remainingCacheExtent + cacheExtentCorrection),
cacheOrigin: corectedCacheOrigin,
), parentUsesSize: true); ), parentUsesSize: true);
final SliverGeometry childLayoutGeometry = child.geometry; final SliverGeometry childLayoutGeometry = child.geometry;
...@@ -304,13 +357,23 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -304,13 +357,23 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
// We use the child's paint origin in our coordinate system as the // We use the child's paint origin in our coordinate system as the
// layoutOffset we store in the child's parent data. // layoutOffset we store in the child's parent data.
final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin; final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin;
updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
// `effectiveLayoutOffset` becomes meaningless once we moved past the trailing edge
// because `childLayoutGeometry.layoutExtent` is zero. Using the still increasing
// 'scrollOffset` to roughly position these invisible slivers in the right order.
if (childLayoutGeometry.visible || scrollOffset > 0) {
updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
} else {
updateChildLayoutOffset(child, -scrollOffset + initialLayoutOffset, growthDirection);
}
maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset); maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset);
scrollOffset -= childLayoutGeometry.scrollExtent; scrollOffset -= childLayoutGeometry.scrollExtent;
layoutOffset += childLayoutGeometry.layoutExtent; layoutOffset += childLayoutGeometry.layoutExtent;
if (childLayoutGeometry.cacheExtent != 0.0) {
if (scrollOffset <= 0.0) remainingCacheExtent -= childLayoutGeometry.cacheExtent - cacheExtentCorrection;
scrollOffset = 0.0; cacheOrigin = math.min(corectedCacheOrigin + childLayoutGeometry.cacheExtent, 0.0);
}
updateOutOfBandData(growthDirection, childLayoutGeometry); updateOutOfBandData(growthDirection, childLayoutGeometry);
...@@ -322,6 +385,59 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -322,6 +385,59 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
return 0.0; return 0.0;
} }
@override
Rect describeApproximatePaintClip(RenderSliver child) {
final Rect viewportClip = Offset.zero & size;
if (child.constraints.overlap == 0) {
return viewportClip;
}
// Adjust the clip rect for this sliver by the overlap from the previous sliver.
double left = viewportClip.left;
double right = viewportClip.right;
double top = viewportClip.top;
double bottom = viewportClip.bottom;
final double startOfOverlap = child.constraints.viewportMainAxisExtent - child.constraints.remainingPaintExtent;
final double overlapCorrection = startOfOverlap + child.constraints.overlap;
switch (applyGrowthDirectionToAxisDirection(axisDirection, child.constraints.growthDirection)) {
case AxisDirection.down:
top += overlapCorrection;
break;
case AxisDirection.up:
bottom -= overlapCorrection;
break;
case AxisDirection.right:
left += overlapCorrection;
break;
case AxisDirection.left:
right -= overlapCorrection;
break;
}
return new Rect.fromLTRB(left, top, right, bottom);
}
@override
Rect describeSemanticsClip(RenderSliver child) {
assert (axis != null);
switch (axis) {
case Axis.vertical:
return new Rect.fromLTRB(
semanticBounds.left,
semanticBounds.top - cacheExtent,
semanticBounds.right,
semanticBounds.bottom + cacheExtent,
);
case Axis.horizontal:
return new Rect.fromLTRB(
semanticBounds.left - cacheExtent,
semanticBounds.top,
semanticBounds.right + cacheExtent,
semanticBounds.bottom,
);
}
return null;
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (firstChild == null) if (firstChild == null)
...@@ -734,11 +850,12 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat ...@@ -734,11 +850,12 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
double anchor: 0.0, double anchor: 0.0,
List<RenderSliver> children, List<RenderSliver> children,
RenderSliver center, RenderSliver center,
double cacheExtent,
}) : assert(anchor != null), }) : assert(anchor != null),
assert(anchor >= 0.0 && anchor <= 1.0), assert(anchor >= 0.0 && anchor <= 1.0),
_anchor = anchor, _anchor = anchor,
_center = center, _center = center,
super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset) { super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset, cacheExtent: cacheExtent) {
addAll(children); addAll(children);
if (center == null && firstChild != null) if (center == null && firstChild != null)
_center = firstChild; _center = firstChild;
...@@ -979,26 +1096,32 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat ...@@ -979,26 +1096,32 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
// centerOffset is the offset from the leading edge of the RenderViewport // centerOffset is the offset from the leading edge of the RenderViewport
// to the zero scroll offset (the line between the forward slivers and the // to the zero scroll offset (the line between the forward slivers and the
// reverse slivers). The other two are that, but clamped to the visible // reverse slivers).
// region of the viewport.
final double centerOffset = mainAxisExtent * anchor - correctedOffset; final double centerOffset = mainAxisExtent * anchor - correctedOffset;
final double clampedForwardCenter = math.max(0.0, math.min(mainAxisExtent, centerOffset)); final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
final double clampedReverseCenter = math.max(0.0, math.min(mainAxisExtent, mainAxisExtent - centerOffset)); final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
final double fullCacheExtent = mainAxisExtent + 2 * cacheExtent;
final double centerCacheOffset = centerOffset + cacheExtent;
final double reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent);
final RenderSliver leadingNegativeChild = childBefore(center); final RenderSliver leadingNegativeChild = childBefore(center);
if (leadingNegativeChild != null) { if (leadingNegativeChild != null) {
// negative scroll offsets // negative scroll offsets
final double result = layoutChildSequence( final double result = layoutChildSequence(
leadingNegativeChild, child: leadingNegativeChild,
math.max(mainAxisExtent, centerOffset) - mainAxisExtent, scrollOffset: math.max(mainAxisExtent, centerOffset) - mainAxisExtent,
0.0, overlap: 0.0,
clampedReverseCenter, layoutOffset: forwardDirectionRemainingPaintExtent,
clampedForwardCenter, remainingPaintExtent: reverseDirectionRemainingPaintExtent,
mainAxisExtent, mainAxisExtent: mainAxisExtent,
crossAxisExtent, crossAxisExtent: crossAxisExtent,
GrowthDirection.reverse, growthDirection: GrowthDirection.reverse,
childBefore, advance: childBefore,
remainingCacheExtent: reverseDirectionRemainingCacheExtent,
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-cacheExtent, 0.0),
); );
if (result != 0.0) if (result != 0.0)
return -result; return -result;
...@@ -1006,15 +1129,17 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat ...@@ -1006,15 +1129,17 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
// positive scroll offsets // positive scroll offsets
return layoutChildSequence( return layoutChildSequence(
center, child: center,
math.max(0.0, -centerOffset), scrollOffset: math.max(0.0, -centerOffset),
leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0, overlap: leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,
clampedForwardCenter, layoutOffset: centerOffset >= mainAxisExtent ? centerOffset: reverseDirectionRemainingPaintExtent,
clampedReverseCenter, remainingPaintExtent: forwardDirectionRemainingPaintExtent,
mainAxisExtent, mainAxisExtent: mainAxisExtent,
crossAxisExtent, crossAxisExtent: crossAxisExtent,
GrowthDirection.forward, growthDirection: GrowthDirection.forward,
childAfter, advance: childAfter,
remainingCacheExtent: forwardDirectionRemainingCacheExtent,
cacheOrigin: centerOffset.clamp(-cacheExtent, 0.0),
); );
} }
...@@ -1333,15 +1458,17 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta ...@@ -1333,15 +1458,17 @@ class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalConta
_shrinkWrapExtent = 0.0; _shrinkWrapExtent = 0.0;
_hasVisualOverflow = false; _hasVisualOverflow = false;
return layoutChildSequence( return layoutChildSequence(
firstChild, child: firstChild,
math.max(0.0, correctedOffset), scrollOffset: math.max(0.0, correctedOffset),
math.min(0.0, correctedOffset), overlap: math.min(0.0, correctedOffset),
0.0, layoutOffset: 0.0,
mainAxisExtent, remainingPaintExtent: mainAxisExtent,
mainAxisExtent, mainAxisExtent: mainAxisExtent,
crossAxisExtent, crossAxisExtent: crossAxisExtent,
GrowthDirection.forward, growthDirection: GrowthDirection.forward,
childAfter, advance: childAfter,
remainingCacheExtent: mainAxisExtent + 2 * cacheExtent,
cacheOrigin: -cacheExtent,
); );
} }
......
...@@ -794,11 +794,39 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -794,11 +794,39 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
} }
} }
/// The clip rect from an ancestor that was applied to this node. /// The semantic clip from an ancestor that was applied to this node.
/// ///
/// Expressed in the coordinate system of the node. May be null if no clip has /// Expressed in the coordinate system of the node. May be null if no clip has
/// been applied. /// been applied.
Rect parentClipRect; ///
/// Descendant [SemanticsNode]s that are positioned outside of this rect will
/// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
/// overlapping with this rect, but are outside of [parentPaintClipRect] will
/// be included in the tree, but they will be marked as hidden because they
/// are assumed to be not visible on screen.
///
/// If this rect is null, all descendant [SemanticsNode]s outside of
/// [parentPaintClipRect] will be excluded from the tree.
///
/// If this rect is non-null it has to completely enclose
/// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
/// also null.
Rect parentSemanticsClipRect;
/// The paint clip from an ancestor that was applied to this node.
///
/// Expressed in the coordinate system of the node. May be null if no clip has
/// been applied.
///
/// Descendant [SemanticsNode]s that are positioned outside of this rect will
/// either be excluded from the semantics tree (if they have no overlap with
/// [parentSemanticsClipRect]) or they will be included and marked as hidden
/// (if they are overlapping with [parentSemanticsClipRect]).
///
/// This rect is completely enclosed by [parentSemanticsClipRect].
///
/// If this rect is null [parentSemanticsClipRect] also has to be null.
Rect parentPaintClipRect;
/// Whether the node is invisible. /// Whether the node is invisible.
/// ///
......
...@@ -564,6 +564,7 @@ class _PageViewState extends State<PageView> { ...@@ -564,6 +564,7 @@ class _PageViewState extends State<PageView> {
physics: physics, physics: physics,
viewportBuilder: (BuildContext context, ViewportOffset position) { viewportBuilder: (BuildContext context, ViewportOffset position) {
return new Viewport( return new Viewport(
cacheExtent: 0.0,
axisDirection: axisDirection, axisDirection: axisDirection,
offset: position, offset: position,
slivers: <Widget>[ slivers: <Widget>[
......
...@@ -56,6 +56,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -56,6 +56,7 @@ abstract class ScrollView extends StatelessWidget {
bool primary, bool primary,
ScrollPhysics physics, ScrollPhysics physics,
this.shrinkWrap: false, this.shrinkWrap: false,
this.cacheExtent,
}) : assert(reverse != null), }) : assert(reverse != null),
assert(shrinkWrap != null), assert(shrinkWrap != null),
assert(!(controller != null && primary == true), assert(!(controller != null && primary == true),
...@@ -165,6 +166,9 @@ abstract class ScrollView extends StatelessWidget { ...@@ -165,6 +166,9 @@ abstract class ScrollView extends StatelessWidget {
/// Defaults to false. /// Defaults to false.
final bool shrinkWrap; final bool shrinkWrap;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Returns the [AxisDirection] in which the scroll view scrolls. /// Returns the [AxisDirection] in which the scroll view scrolls.
/// ///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
...@@ -211,6 +215,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -211,6 +215,7 @@ abstract class ScrollView extends StatelessWidget {
axisDirection: axisDirection, axisDirection: axisDirection,
offset: offset, offset: offset,
slivers: slivers, slivers: slivers,
cacheExtent: cacheExtent,
); );
} }
...@@ -333,6 +338,7 @@ class CustomScrollView extends ScrollView { ...@@ -333,6 +338,7 @@ class CustomScrollView extends ScrollView {
bool primary, bool primary,
ScrollPhysics physics, ScrollPhysics physics,
bool shrinkWrap: false, bool shrinkWrap: false,
double cacheExtent,
this.slivers: const <Widget>[], this.slivers: const <Widget>[],
}) : super( }) : super(
key: key, key: key,
...@@ -342,6 +348,7 @@ class CustomScrollView extends ScrollView { ...@@ -342,6 +348,7 @@ class CustomScrollView extends ScrollView {
primary: primary, primary: primary,
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
); );
/// The slivers to place inside the viewport. /// The slivers to place inside the viewport.
...@@ -372,6 +379,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -372,6 +379,7 @@ abstract class BoxScrollView extends ScrollView {
ScrollPhysics physics, ScrollPhysics physics,
bool shrinkWrap: false, bool shrinkWrap: false,
this.padding, this.padding,
double cacheExtent,
}) : super( }) : super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
...@@ -380,6 +388,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -380,6 +388,7 @@ abstract class BoxScrollView extends ScrollView {
primary: primary, primary: primary,
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
); );
/// The amount of space by which to inset the children. /// The amount of space by which to inset the children.
...@@ -594,6 +603,7 @@ class ListView extends BoxScrollView { ...@@ -594,6 +603,7 @@ class ListView extends BoxScrollView {
this.itemExtent, this.itemExtent,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate( }) : childrenDelegate = new SliverChildListDelegate(
children, children,
...@@ -608,6 +618,7 @@ class ListView extends BoxScrollView { ...@@ -608,6 +618,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, linear array of widgets that are created on demand. /// Creates a scrollable, linear array of widgets that are created on demand.
...@@ -648,6 +659,7 @@ class ListView extends BoxScrollView { ...@@ -648,6 +659,7 @@ class ListView extends BoxScrollView {
int itemCount, int itemCount,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
}) : childrenDelegate = new SliverChildBuilderDelegate( }) : childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder, itemBuilder,
childCount: itemCount, childCount: itemCount,
...@@ -662,6 +674,7 @@ class ListView extends BoxScrollView { ...@@ -662,6 +674,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent
); );
/// Creates a scrollable, linear array of widgets with a custom child model. /// Creates a scrollable, linear array of widgets with a custom child model.
...@@ -679,6 +692,7 @@ class ListView extends BoxScrollView { ...@@ -679,6 +692,7 @@ class ListView extends BoxScrollView {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
this.itemExtent, this.itemExtent,
@required this.childrenDelegate, @required this.childrenDelegate,
double cacheExtent,
}) : assert(childrenDelegate != null), }) : assert(childrenDelegate != null),
super( super(
key: key, key: key,
...@@ -689,6 +703,7 @@ class ListView extends BoxScrollView { ...@@ -689,6 +703,7 @@ class ListView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// If non-null, forces the children to have the given extent in the scroll /// If non-null, forces the children to have the given extent in the scroll
...@@ -878,6 +893,7 @@ class GridView extends BoxScrollView { ...@@ -878,6 +893,7 @@ class GridView extends BoxScrollView {
@required this.gridDelegate, @required this.gridDelegate,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildListDelegate( childrenDelegate = new SliverChildListDelegate(
...@@ -894,6 +910,7 @@ class GridView extends BoxScrollView { ...@@ -894,6 +910,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets that are created on demand. /// Creates a scrollable, 2D array of widgets that are created on demand.
...@@ -929,6 +946,7 @@ class GridView extends BoxScrollView { ...@@ -929,6 +946,7 @@ class GridView extends BoxScrollView {
int itemCount, int itemCount,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildBuilderDelegate( childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder, itemBuilder,
...@@ -945,6 +963,7 @@ class GridView extends BoxScrollView { ...@@ -945,6 +963,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with both a custom /// Creates a scrollable, 2D array of widgets with both a custom
...@@ -965,6 +984,7 @@ class GridView extends BoxScrollView { ...@@ -965,6 +984,7 @@ class GridView extends BoxScrollView {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
@required this.gridDelegate, @required this.gridDelegate,
@required this.childrenDelegate, @required this.childrenDelegate,
double cacheExtent,
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
assert(childrenDelegate != null), assert(childrenDelegate != null),
super( super(
...@@ -976,6 +996,7 @@ class GridView extends BoxScrollView { ...@@ -976,6 +996,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
...@@ -1007,6 +1028,7 @@ class GridView extends BoxScrollView { ...@@ -1007,6 +1028,7 @@ class GridView extends BoxScrollView {
double childAspectRatio: 1.0, double childAspectRatio: 1.0,
bool addAutomaticKeepAlives: true, bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true, bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount( }) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
...@@ -1027,6 +1049,7 @@ class GridView extends BoxScrollView { ...@@ -1027,6 +1049,7 @@ class GridView extends BoxScrollView {
physics: physics, physics: physics,
shrinkWrap: shrinkWrap, shrinkWrap: shrinkWrap,
padding: padding, padding: padding,
cacheExtent: cacheExtent,
); );
/// Creates a scrollable, 2D array of widgets with tiles that each have a /// Creates a scrollable, 2D array of widgets with tiles that each have a
......
...@@ -323,11 +323,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -323,11 +323,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
_RenderSingleChildViewport({ _RenderSingleChildViewport({
AxisDirection axisDirection: AxisDirection.down, AxisDirection axisDirection: AxisDirection.down,
@required ViewportOffset offset, @required ViewportOffset offset,
double cacheExtent: RenderAbstractViewport.defaultCacheExtent,
RenderBox child, RenderBox child,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(offset != null), assert(offset != null),
assert(cacheExtent != null),
_axisDirection = axisDirection, _axisDirection = axisDirection,
_offset = offset { _offset = offset,
_cacheExtent = cacheExtent {
this.child = child; this.child = child;
} }
...@@ -357,6 +360,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -357,6 +360,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
markNeedsLayout(); markNeedsLayout();
} }
/// {@macro flutter.rendering.viewport.cacheExtent}
double get cacheExtent => _cacheExtent;
double _cacheExtent;
set cacheExtent(double value) {
assert(value != null);
if (value == _cacheExtent)
return;
_cacheExtent = value;
markNeedsLayout();
}
void _hasScrolled() { void _hasScrolled() {
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
...@@ -588,4 +602,26 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -588,4 +602,26 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
// Make sure the viewport itself is on screen. // Make sure the viewport itself is on screen.
super.showOnScreen(); super.showOnScreen();
} }
@override
Rect describeSemanticsClip(RenderObject child) {
assert(axis != null);
switch (axis) {
case Axis.vertical:
return new Rect.fromLTRB(
semanticBounds.left,
semanticBounds.top - cacheExtent,
semanticBounds.right,
semanticBounds.bottom + cacheExtent,
);
case Axis.horizontal:
return new Rect.fromLTRB(
semanticBounds.left - cacheExtent,
semanticBounds.top,
semanticBounds.right + cacheExtent,
semanticBounds.bottom,
);
}
return null;
}
} }
...@@ -888,6 +888,25 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -888,6 +888,25 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
assert(!_childElements.values.any((Element child) => child == null)); assert(!_childElements.values.any((Element child) => child == null));
_childElements.values.toList().forEach(visitor); _childElements.values.toList().forEach(visitor);
} }
@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);
}
} }
/// A sliver that contains a single box child that fills the remaining space in /// A sliver that contains a single box child that fills the remaining space in
......
...@@ -56,6 +56,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -56,6 +56,7 @@ class Viewport extends MultiChildRenderObjectWidget {
this.anchor: 0.0, this.anchor: 0.0,
@required this.offset, @required this.offset,
this.center, this.center,
this.cacheExtent,
List<Widget> slivers: const <Widget>[], List<Widget> slivers: const <Widget>[],
}) : assert(offset != null), }) : assert(offset != null),
assert(slivers != null), assert(slivers != null),
...@@ -108,6 +109,9 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -108,6 +109,9 @@ class Viewport extends MultiChildRenderObjectWidget {
/// The [center] must be the key of a child of the viewport. /// The [center] must be the key of a child of the viewport.
final Key center; final Key center;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross /// Given a [BuildContext] and an [AxisDirection], determine the correct cross
/// axis direction. /// axis direction.
/// ///
...@@ -135,6 +139,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -135,6 +139,7 @@ class Viewport extends MultiChildRenderObjectWidget {
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor, anchor: anchor,
offset: offset, offset: offset,
cacheExtent: cacheExtent,
); );
} }
...@@ -144,7 +149,8 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -144,7 +149,8 @@ class Viewport extends MultiChildRenderObjectWidget {
..axisDirection = axisDirection ..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
..anchor = anchor ..anchor = anchor
..offset = offset; ..offset = offset
..cacheExtent = cacheExtent;
} }
@override @override
...@@ -199,6 +205,14 @@ class _ViewportElement extends MultiChildRenderObjectElement { ...@@ -199,6 +205,14 @@ class _ViewportElement extends MultiChildRenderObjectElement {
renderObject.center = null; renderObject.center = null;
} }
} }
@override
void debugVisitOnstageChildren(ElementVisitor visitor) {
children.where((Element e) {
final RenderSliver renderSliver = e.renderObject;
return renderSliver.geometry.visible;
}).forEach(visitor);
}
} }
/// A widget that is bigger on the inside and shrink wraps its children in the /// A widget that is bigger on the inside and shrink wraps its children in the
......
...@@ -430,11 +430,11 @@ void main() { ...@@ -430,11 +430,11 @@ void main() {
// Now the sliver is scrolled off screen. // Now the sliver is scrolled off screen.
expect( expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy, tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536), moreOrLessEquals(-175.38461538461536),
); );
expect( expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy, tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536), moreOrLessEquals(-115.38461538461536),
); );
expect( expect(
...@@ -720,11 +720,11 @@ void main() { ...@@ -720,11 +720,11 @@ void main() {
// Now the sliver is scrolled off screen. // Now the sliver is scrolled off screen.
expect( expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy, tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536), moreOrLessEquals(-175.38461538461536),
); );
expect( expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy, tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536), moreOrLessEquals(-115.38461538461536),
); );
...@@ -872,7 +872,7 @@ void main() { ...@@ -872,7 +872,7 @@ void main() {
); );
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
...@@ -907,7 +907,7 @@ void main() { ...@@ -907,7 +907,7 @@ void main() {
await tester.pump(const Duration(seconds: 2)); await tester.pump(const Duration(seconds: 2));
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
...@@ -1147,7 +1147,7 @@ void main() { ...@@ -1147,7 +1147,7 @@ void main() {
moreOrLessEquals(-145.0332383665717), moreOrLessEquals(-145.0332383665717),
); );
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh, RefreshIndicatorMode.refresh,
); );
...@@ -1155,7 +1155,7 @@ void main() { ...@@ -1155,7 +1155,7 @@ void main() {
// The sliver layout extent is removed on next frame. // The sliver layout extent is removed on next frame.
await tester.pump(); await tester.pump();
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
// Nothing moved. // Nothing moved.
...@@ -1208,7 +1208,7 @@ void main() { ...@@ -1208,7 +1208,7 @@ void main() {
await tester.pump(const Duration(seconds: 5)); await tester.pump(const Duration(seconds: 5));
// In refresh mode but has no UI. // In refresh mode but has no UI.
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh, RefreshIndicatorMode.refresh,
); );
expect( expect(
...@@ -1221,7 +1221,7 @@ void main() { ...@@ -1221,7 +1221,7 @@ void main() {
await tester.pump(); await tester.pump();
// Goes to inactive right away since the sliver is already collapsed. // Goes to inactive right away since the sliver is already collapsed.
expect( expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))), CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive, RefreshIndicatorMode.inactive,
); );
......
...@@ -54,16 +54,11 @@ ScrollController primaryScrollController(WidgetTester tester) { ...@@ -54,16 +54,11 @@ ScrollController primaryScrollController(WidgetTester tester) {
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView))); return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
} }
bool appBarIsVisible(WidgetTester tester) { double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
final RenderSliver sliver = tester.element(find.byType(SliverAppBar)).findRenderObject(); double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar, skipOffstage: false)).dy;
return sliver.geometry.visible; double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar, skipOffstage: false)).dy;
}
double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar)).height;
double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar)).dy;
double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar)).dy;
double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar)).height; double tabBarHeight(WidgetTester tester) => tester.getSize(find.byType(TabBar, skipOffstage: false)).height;
void main() { void main() {
setUp(() { setUp(() {
...@@ -592,7 +587,7 @@ void main() { ...@@ -592,7 +587,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
final double initialAppBarHeight = appBarHeight(tester); final double initialAppBarHeight = appBarHeight(tester);
final double initialTabBarHeight = tabBarHeight(tester); final double initialTabBarHeight = tabBarHeight(tester);
...@@ -600,21 +595,21 @@ void main() { ...@@ -600,21 +595,21 @@ void main() {
// Scroll the not-pinned appbar partially out of view // Scroll the not-pinned appbar partially out of view
controller.jumpTo(50.0); controller.jumpTo(50.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar out of view // Scroll the not-pinned appbar out of view
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), false); expect(find.byType(SliverAppBar), findsNothing);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar back into view // Scroll the not-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -629,7 +624,7 @@ void main() { ...@@ -629,7 +624,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0; const double initialAppBarHeight = 128.0;
...@@ -639,7 +634,7 @@ void main() { ...@@ -639,7 +634,7 @@ void main() {
// point both the toolbar and the tabbar are visible. // point both the toolbar and the tabbar are visible.
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight)); expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), greaterThan(initialTabBarHeight)); expect(appBarHeight(tester), greaterThan(initialTabBarHeight));
...@@ -647,7 +642,7 @@ void main() { ...@@ -647,7 +642,7 @@ void main() {
// Scroll the not-pinned appbar back into view // Scroll the not-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -662,7 +657,7 @@ void main() { ...@@ -662,7 +657,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester); final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0); expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0; const double initialAppBarHeight = 128.0;
...@@ -672,7 +667,7 @@ void main() { ...@@ -672,7 +667,7 @@ void main() {
// point only the tabBar is visible. // point only the tabBar is visible.
controller.jumpTo(600.0); controller.jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight)); expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), initialTabBarHeight); expect(appBarHeight(tester), initialTabBarHeight);
...@@ -680,7 +675,7 @@ void main() { ...@@ -680,7 +675,7 @@ void main() {
// Scroll the floating-pinned appbar back into view // Scroll the floating-pinned appbar back into view
controller.jumpTo(0.0); controller.jumpTo(0.0);
await tester.pump(); await tester.pump();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight); expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
...@@ -692,7 +687,7 @@ void main() { ...@@ -692,7 +687,7 @@ void main() {
snap: true, snap: true,
expandedHeight: 128.0, expandedHeight: 128.0,
)); ));
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0); expect(appBarBottom(tester), 128.0);
...@@ -701,7 +696,7 @@ void main() { ...@@ -701,7 +696,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.00); position.jumpTo(256.00);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(appBarIsVisible(tester), false); expect(find.byType(SliverAppBar), findsNothing);
expect(appBarTop(tester), lessThanOrEqualTo(-128.0)); expect(appBarTop(tester), lessThanOrEqualTo(-128.0));
// Drag the scrollable up and down. The app bar should not snap open, its // Drag the scrollable up and down. The app bar should not snap open, its
...@@ -773,7 +768,7 @@ void main() { ...@@ -773,7 +768,7 @@ void main() {
snap: true, snap: true,
expandedHeight: 128.0, expandedHeight: 128.0,
)); ));
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0); expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0); expect(appBarBottom(tester), 128.0);
...@@ -783,7 +778,7 @@ void main() { ...@@ -783,7 +778,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.0); position.jumpTo(256.0);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(appBarIsVisible(tester), true); expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0); expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), kTextTabBarHeight); expect(appBarHeight(tester), kTextTabBarHeight);
......
...@@ -289,7 +289,7 @@ void main() { ...@@ -289,7 +289,7 @@ void main() {
} }
StateMarkerState findStateMarkerState(String name) { StateMarkerState findStateMarkerState(String name) {
return tester.state(find.widgetWithText(StateMarker, name)); return tester.state(find.widgetWithText(StateMarker, name, skipOffstage: false));
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
......
...@@ -162,7 +162,7 @@ void main() { ...@@ -162,7 +162,7 @@ void main() {
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump(); await tester.pump();
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
expect(tester.testTextInput.isVisible, isTrue); expect(tester.testTextInput.isVisible, isTrue);
focusNode.unfocus(); focusNode.unfocus();
...@@ -201,10 +201,10 @@ void main() { ...@@ -201,10 +201,10 @@ void main() {
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField), findsOneWidget);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump(); await tester.pump();
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
await tester.pumpWidget(makeTest('test')); await tester.pumpWidget(makeTest('test'));
await tester.pump(); // in case the AutomaticKeepAlive widget thinks it needs a cleanup frame await tester.pump(); // in case the AutomaticKeepAlive widget thinks it needs a cleanup frame
expect(find.byType(TextField), findsOneWidget); expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
}); });
testWidgets('TextField with decoration:null', (WidgetTester tester) async { testWidgets('TextField with decoration:null', (WidgetTester tester) async {
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
void main() {
test('RenderViewport calculates correct constraints, RenderSliverToBoxAdapter calculates correct geometry', () {
final List<RenderSliver> children = new List<RenderSliver>.generate(30, (int index) {
return new RenderSliverToBoxAdapter(
child: new RenderSizedBox(const Size(400.0, 100.0)),
);
});
// Viewport is 800x600, can show 6 children at a time.
final RenderViewport root = new RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 250.0,
children: children,
);
layout(root);
RenderSliver firstVisible = children[0];
expectSliverConstraints(
sliver: firstVisible,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstVisible,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
RenderSliver lastVisible = children[5];
expectSliverConstraints(
sliver: lastVisible,
cacheOrigin: 0.0,
remainingPaintExtent: 100.0,
remainingCacheExtent: 350.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisible,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
RenderSliver firstInCache = children[6];
expectSliverConstraints(
sliver: firstInCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstInCache,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
RenderSliver lastInCache = children[8];
expectSliverConstraints(
sliver: lastInCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 50.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastInCache,
paintExtent: 0.0,
cacheExtent: 50.0,
visible: false,
);
RenderSliver outsideCache = children[9];
expectSliverConstraints(
sliver: outsideCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 0.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: outsideCache,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
// scroll down half a sliver
root.offset = new ViewportOffset.fixed(50.0);
pumpFrame();
firstVisible = children[0];
expectSliverConstraints(
sliver: firstVisible,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: firstVisible,
paintExtent: 50.0,
cacheExtent: 100.0,
visible: true,
);
lastVisible = children[6];
expectSliverConstraints(
sliver: lastVisible,
cacheOrigin: 0.0,
remainingPaintExtent: 50.0,
remainingCacheExtent: 300.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisible,
paintExtent: 50.0,
cacheExtent: 100.0,
visible: true,
);
firstInCache = children[7];
expectSliverConstraints(
sliver: firstInCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 200.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstInCache,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
lastInCache = children[8];
expectSliverConstraints(
sliver: lastInCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 100.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastInCache,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
outsideCache = children[9];
expectSliverConstraints(
sliver: outsideCache,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 0.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: outsideCache,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
// scroll down 1.5 slivers
root.offset = new ViewportOffset.fixed(150.0);
pumpFrame();
RenderSliver firstInPreCache = children[0];
expectSliverConstraints(
sliver: firstInPreCache,
cacheOrigin: -150.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 150.0 + 600.0 + 250.0,
scrollOffset: 150.0,
);
expectSliverGeometry(
sliver: firstInPreCache,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
firstVisible = children[1];
expectSliverConstraints(
sliver: firstVisible,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: firstVisible,
paintExtent: 50.0,
cacheExtent: 100.0,
visible: true,
);
// scroll down 10 slivers
root.offset = new ViewportOffset.fixed(1000.0);
pumpFrame();
final RenderSliver first = children[0];
expectSliverConstraints(
sliver: first,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 1000.0,
);
expectSliverGeometry(
sliver: first,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
firstInPreCache = children[7];
expectSliverConstraints(
sliver: firstInPreCache,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 300.0,
);
expectSliverGeometry(
sliver: firstInPreCache,
paintExtent: 0.0,
cacheExtent: 50.0,
visible: false,
);
final RenderSliver lastInPreCache = children[9];
expectSliverConstraints(
sliver: lastInPreCache,
cacheOrigin: -100.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 100.0 + 600.0 + 250.0,
scrollOffset: 100.0,
);
expectSliverGeometry(
sliver: lastInPreCache,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
firstVisible = children[10];
expectSliverConstraints(
sliver: firstVisible,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstVisible,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
});
test('RenderSliverFixedExtentList calculates correct geometry', () {
// Viewport is 800x600, can show 6 full children at a time
final List<RenderBox> children = new List<RenderBox>.generate(30, (int index) {
return new RenderSizedBox(const Size(400.0, 100.0));
});
final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
children: children,
);
RenderSliverFixedExtentList inner;
final RenderViewport root = new RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[
inner = childManager.createRenderSliverFixedExtentList(),
],
);
layout(root);
expectSliverConstraints(
sliver: inner,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
// scroll half an item down
root.offset = new ViewportOffset.fixed(50.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 900.0,
visible: true,
);
expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
// scroll to the middle
root.offset = new ViewportOffset.fixed(1500.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 1500.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 1100.0,
visible: true,
);
expect(children.sublist(0, 12).any((RenderBox r) => r.attached), false);
expect(children.sublist(12, 24).every((RenderBox r) => r.attached), true);
expect(children.sublist(24, 30).any((RenderBox r) => r.attached), false);
// scroll to the end
root.offset = new ViewportOffset.fixed(2400.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 2400.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 21).any((RenderBox r) => r.attached), false);
expect(children.sublist(21, 30).every((RenderBox r) => r.attached), true);
});
test('RenderSliverList calculates correct geometry', () {
// Viewport is 800x600, can show 6 full children at a time
final List<RenderBox> children = new List<RenderBox>.generate(30, (int index) {
return new RenderSizedBox(const Size(400.0, 100.0));
});
final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
children: children,
);
RenderSliverList inner;
final RenderViewport root = new RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[
inner = childManager.createRenderSliverList(),
],
);
layout(root);
expectSliverConstraints(
sliver: inner,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
// scroll half an item down
root.offset = new ViewportOffset.fixed(50.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 900.0,
visible: true,
);
expect(children.sublist(0, 9).every((RenderBox r) => r.attached), true);
expect(children.sublist(9, 30).any((RenderBox r) => r.attached), false);
// scroll to the middle
root.offset = new ViewportOffset.fixed(1500.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 1500.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 1100.0,
visible: true,
);
expect(children.sublist(0, 12).any((RenderBox r) => r.attached), false);
expect(children.sublist(12, 24).every((RenderBox r) => r.attached), true);
expect(children.sublist(24, 30).any((RenderBox r) => r.attached), false);
// scroll to the end
root.offset = new ViewportOffset.fixed(2400.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 2400.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 21).any((RenderBox r) => r.attached), false);
expect(children.sublist(21, 30).every((RenderBox r) => r.attached), true);
});
test('RenderSliverGrid calculates correct geometry', () {
// Viewport is 800x600, each grid element is 400x100, giving us space for 12 visible children
final List<RenderBox> children = new List<RenderBox>.generate(60, (int index) {
return new RenderSizedBox(const Size(400.0, 100.0));
});
final TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager(
children: children,
);
RenderSliverGrid inner;
final RenderViewport root = new RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[
inner = childManager.createRenderSliverGrid(),
],
);
layout(root);
expectSliverConstraints(
sliver: inner,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 18).every((RenderBox r) => r.attached), true);
expect(children.sublist(18, 60).any((RenderBox r) => r.attached), false);
// scroll half an item down
root.offset = new ViewportOffset.fixed(50.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 900.0,
visible: true,
);
expect(children.sublist(0, 18).every((RenderBox r) => r.attached), true);
expect(children.sublist(18, 60).any((RenderBox r) => r.attached), false);
// scroll to the middle
root.offset = new ViewportOffset.fixed(1500.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 1500.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 1100.0,
visible: true,
);
expect(children.sublist(0, 24).any((RenderBox r) => r.attached), false);
expect(children.sublist(24, 48).every((RenderBox r) => r.attached), true);
expect(children.sublist(48, 60).any((RenderBox r) => r.attached), false);
// scroll to the end
root.offset = new ViewportOffset.fixed(2400.0);
pumpFrame();
expectSliverConstraints(
sliver: inner,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 2400.0,
);
expectSliverGeometry(
sliver: inner,
paintExtent: 600.0,
cacheExtent: 850.0,
visible: true,
);
expect(children.sublist(0, 42).any((RenderBox r) => r.attached), false);
expect(children.sublist(42, 60).every((RenderBox r) => r.attached), true);
});
test('RenderSliverPadding calculates correct geometry', () {
// Viewport is 800x600, each item is 100px high with 50px before and after = 200px
final List<RenderSliverToBoxAdapter> adapters = <RenderSliverToBoxAdapter>[];
final List<RenderSliverPadding> paddings = new List<RenderSliverPadding>.generate(30, (int index) {
RenderSliverToBoxAdapter adapter;
final RenderSliverPadding padding = new RenderSliverPadding(
padding: const EdgeInsets.symmetric(vertical: 50.0),
child: adapter = new RenderSliverToBoxAdapter(
child: new RenderSizedBox(const Size(400.0, 100.0)),
),
);
adapters.add(adapter);
return padding;
});
final RenderViewport root = new RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 250.0,
children: paddings,
);
layout(root);
RenderSliverPadding firstVisiblePadding = paddings[0];
expectSliverConstraints(
sliver: firstVisiblePadding,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstVisiblePadding,
paintExtent: 200.0,
cacheExtent: 200.0,
visible: true,
);
RenderSliverToBoxAdapter firstVisiblePadded = adapters[0];
expectSliverConstraints(
sliver: firstVisiblePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 550.0,
remainingCacheExtent: 600.0 + 250.0 - 50.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstVisiblePadded,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
RenderSliverPadding lastVisiblePadding = paddings[2];
expectSliverConstraints(
sliver: lastVisiblePadding,
cacheOrigin: 0.0,
remainingPaintExtent: 200.0,
remainingCacheExtent: 200.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisiblePadding,
paintExtent: 200.0,
cacheExtent: 200.0,
visible: true,
);
RenderSliverToBoxAdapter lastVisiblePadded = adapters[2];
expectSliverConstraints(
sliver: lastVisiblePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 150.0,
remainingCacheExtent: 200.0 + 250.0 - 50.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisiblePadded,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
final RenderSliverPadding firstCachePadding = paddings[3];
expectSliverConstraints(
sliver: firstCachePadding,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstCachePadding,
paintExtent: 0.0,
cacheExtent: 200.0,
visible: false,
);
final RenderSliverToBoxAdapter firstCachePadded = adapters[3];
expectSliverConstraints(
sliver: firstCachePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 250.0 - 50.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstCachePadded,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
final RenderSliverPadding lastCachePadding = paddings[4];
expectSliverConstraints(
sliver: lastCachePadding,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 50.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastCachePadding,
paintExtent: 0.0,
cacheExtent: 50.0,
visible: false,
);
final RenderSliverToBoxAdapter lastCachePadded = adapters[4];
expectSliverConstraints(
sliver: lastCachePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 0.0,
remainingCacheExtent: 0.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastCachePadded,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
// scroll first padding off screen
root.offset = new ViewportOffset.fixed(50.0);
pumpFrame();
firstVisiblePadding = paddings[0];
expectSliverConstraints(
sliver: firstVisiblePadding,
cacheOrigin: -50.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 50.0 + 600.0 + 250.0,
scrollOffset: 50.0,
);
expectSliverGeometry(
sliver: firstVisiblePadding,
paintExtent: 150.0,
cacheExtent: 200.0,
visible: true,
);
firstVisiblePadded = adapters[0];
expectSliverConstraints(
sliver: firstVisiblePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 600.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: firstVisiblePadded,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
// scroll to the end
root.offset = new ViewportOffset.fixed(5400.0);
pumpFrame();
final RenderSliverPadding firstPadding = paddings[0];
expectSliverConstraints(
sliver: firstPadding,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 5400.0,
);
expectSliverGeometry(
sliver: firstPadding,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
final RenderSliverToBoxAdapter firstPadded = adapters[0];
expectSliverConstraints(
sliver: firstPadded,
cacheOrigin: -200.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 5350.0,
);
expectSliverGeometry(
sliver: firstPadded,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
final RenderSliverPadding firstPreCachePadding = paddings[25];
expectSliverConstraints(
sliver: firstPreCachePadding,
cacheOrigin: -250.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 400.0,
);
expectSliverGeometry(
sliver: firstPreCachePadding,
paintExtent: 0.0,
cacheExtent: 50.0,
visible: false,
);
final RenderSliverToBoxAdapter firstPreCachePadded = adapters[25];
expectSliverConstraints(
sliver: firstPreCachePadded,
cacheOrigin: -200.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 250.0 + 600.0 + 250.0,
scrollOffset: 350.0,
);
expectSliverGeometry(
sliver: firstPreCachePadded,
paintExtent: 0.0,
cacheExtent: 0.0,
visible: false,
);
final RenderSliverPadding lastPreCachePadding = paddings[26];
expectSliverConstraints(
sliver: lastPreCachePadding,
cacheOrigin: -200.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 200.0 + 600.0 + 250.0,
scrollOffset: 200.0,
);
expectSliverGeometry(
sliver: lastPreCachePadding,
paintExtent: 0.0,
cacheExtent: 200.0,
visible: false,
);
final RenderSliverToBoxAdapter lastPreCachePadded = adapters[26];
expectSliverConstraints(
sliver: lastPreCachePadded,
cacheOrigin: -150.0,
remainingPaintExtent: 600.0,
remainingCacheExtent: 150.0 + 600.0 + 250.0,
scrollOffset: 150.0,
);
expectSliverGeometry(
sliver: lastPreCachePadded,
paintExtent: 0.0,
cacheExtent: 100.0,
visible: false,
);
lastVisiblePadding = paddings[29];
expectSliverConstraints(
sliver: lastVisiblePadding,
cacheOrigin: 0.0,
remainingPaintExtent: 200.0,
remainingCacheExtent: 200.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisiblePadding,
paintExtent: 200.0,
cacheExtent: 200.0,
visible: true,
);
lastVisiblePadded = adapters[29];
expectSliverConstraints(
sliver: lastVisiblePadded,
cacheOrigin: 0.0,
remainingPaintExtent: 150.0,
remainingCacheExtent: 150.0 + 250.0,
scrollOffset: 0.0,
);
expectSliverGeometry(
sliver: lastVisiblePadded,
paintExtent: 100.0,
cacheExtent: 100.0,
visible: true,
);
});
}
void expectSliverConstraints({RenderSliver sliver, double cacheOrigin, double remainingPaintExtent, double remainingCacheExtent, double scrollOffset}) {
expect(sliver.constraints.cacheOrigin, cacheOrigin, reason: 'cacheOrigin');
expect(sliver.constraints.remainingPaintExtent, remainingPaintExtent, reason: 'remainingPaintExtent');
expect(sliver.constraints.remainingCacheExtent, remainingCacheExtent, reason: 'remainingCacheExtent');
expect(sliver.constraints.scrollOffset, scrollOffset, reason: 'scrollOffset');
}
void expectSliverGeometry({RenderSliver sliver, double paintExtent, double cacheExtent, bool visible}) {
expect(sliver.geometry.paintExtent, paintExtent, reason: 'paintExtent');
expect(sliver.geometry.cacheExtent, cacheExtent, reason: 'cacheExtent');
expect(sliver.geometry.visible, visible, reason: 'visible');
}
class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager {
TestRenderSliverBoxChildManager({
this.children,
});
RenderSliverMultiBoxAdaptor _renderObject;
List<RenderBox> children;
RenderSliverList createRenderSliverList() {
assert(_renderObject == null);
_renderObject = new RenderSliverList(childManager: this);
return _renderObject;
}
RenderSliverFixedExtentList createRenderSliverFixedExtentList() {
assert(_renderObject == null);
_renderObject = new RenderSliverFixedExtentList(
childManager: this,
itemExtent: 100.0,
);
return _renderObject;
}
RenderSliverGrid createRenderSliverGrid() {
assert(_renderObject == null);
_renderObject = new RenderSliverGrid(
childManager: this,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4.0,
),
);
return _renderObject;
}
int _currentlyUpdatingChildIndex;
@override
void createChild(int index, { @required RenderBox after }) {
if (index < 0 || index >= children.length)
return null;
try {
_currentlyUpdatingChildIndex = index;
_renderObject.insert(children[index], after: after);
} finally {
_currentlyUpdatingChildIndex = null;
}
}
@override
void removeChild(RenderBox child) {
_renderObject.remove(child);
}
@override
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
assert(lastIndex >= firstIndex);
return children.length * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1);
}
@override
int get childCount => children.length;
@override
void didAdoptChild(RenderBox child) {
assert(_currentlyUpdatingChildIndex != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
childParentData.index = _currentlyUpdatingChildIndex;
}
@override
void setDidUnderflow(bool value) { }
}
...@@ -83,6 +83,7 @@ void main() { ...@@ -83,6 +83,7 @@ void main() {
axisDirection: AxisDirection.down, axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(), offset: new ViewportOffset.zero(),
cacheExtent: 0.0,
children: <RenderSliver>[ children: <RenderSliver>[
inner = childManager.createRenderObject(), inner = childManager.createRenderObject(),
], ],
...@@ -161,6 +162,7 @@ void main() { ...@@ -161,6 +162,7 @@ void main() {
children: <RenderSliver>[ children: <RenderSliver>[
inner = childManager.createRenderObject(), inner = childManager.createRenderObject(),
], ],
cacheExtent: 0.0,
); );
layout(root); layout(root);
......
...@@ -27,6 +27,8 @@ void main() { ...@@ -27,6 +27,8 @@ void main() {
crossAxisExtent: 0.0, crossAxisExtent: 0.0,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 0.0, viewportMainAxisExtent: 0.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
); );
final SliverConstraints b = a.copyWith(); final SliverConstraints b = a.copyWith();
expect(a, equals(b)); expect(a, equals(b));
...@@ -55,6 +57,8 @@ void main() { ...@@ -55,6 +57,8 @@ void main() {
crossAxisExtent: 40.0, crossAxisExtent: 40.0,
crossAxisDirection: AxisDirection.right, crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 30.0, viewportMainAxisExtent: 30.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
); );
expect(c, equals(d)); expect(c, equals(d));
expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward)); expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward));
......
...@@ -86,9 +86,10 @@ void main() { ...@@ -86,9 +86,10 @@ void main() {
' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ │ crossAxisDirection: AxisDirection.right,\n' ' │ │ crossAxisDirection: AxisDirection.right,\n'
' │ │ viewportMainAxisExtent: 600.0)\n' ' │ │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ │ cacheOrigin: 0.0 )\n'
' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 400.0,\n' ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 400.0,\n'
' │ │ maxPaintExtent: 400.0)\n' ' │ │ maxPaintExtent: 400.0, cacheExtent: 400.0)\n'
' │ │\n' ' │ │\n'
' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n' ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n' ' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
...@@ -101,9 +102,11 @@ void main() { ...@@ -101,9 +102,11 @@ void main() {
' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ │ 0.0, remainingPaintExtent: 200.0, crossAxisExtent: 800.0,\n' ' │ │ 0.0, remainingPaintExtent: 200.0, crossAxisExtent: 800.0,\n'
' │ │ crossAxisDirection: AxisDirection.right,\n' ' │ │ crossAxisDirection: AxisDirection.right,\n'
' │ │ viewportMainAxisExtent: 600.0)\n' ' │ │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 450.0\n'
' │ │ cacheOrigin: 0.0 )\n'
' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 200.0,\n' ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, paintExtent: 200.0,\n'
' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n' ' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true, cacheExtent:\n'
' │ │ 400.0)\n'
' │ │\n' ' │ │\n'
' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n' ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n' ' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
...@@ -111,14 +114,16 @@ void main() { ...@@ -111,14 +114,16 @@ void main() {
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' ├─child 2: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n' ' ├─child 2: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' │ │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n' ' │ │ parentData: paintOffset=Offset(0.0, 800.0) (can use size)\n'
' │ │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n' ' │ │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
' │ │ crossAxisDirection: AxisDirection.right,\n' ' │ │ crossAxisDirection: AxisDirection.right,\n'
' │ │ viewportMainAxisExtent: 600.0)\n' ' │ │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 50.0\n'
' │ │ cacheOrigin: 0.0 )\n'
' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n' ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n' ' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true, cacheExtent:\n'
' │ │ 50.0)\n'
' │ │\n' ' │ │\n'
' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n' ' │ └─child: RenderSizedBox#00000 NEEDS-PAINT\n'
' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n' ' │ parentData: paintOffset=Offset(0.0, -0.0) (can use size)\n'
...@@ -126,12 +131,13 @@ void main() { ...@@ -126,12 +131,13 @@ void main() {
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' ├─child 3: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n' ' ├─child 3: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' │ │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n' ' │ │ parentData: paintOffset=Offset(0.0, 1200.0) (can use size)\n'
' │ │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n' ' │ │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
' │ │ crossAxisDirection: AxisDirection.right,\n' ' │ │ crossAxisDirection: AxisDirection.right,\n'
' │ │ viewportMainAxisExtent: 600.0)\n' ' │ │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 0.0\n'
' │ │ cacheOrigin: 0.0 )\n'
' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n' ' │ │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n' ' │ │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
' │ │\n' ' │ │\n'
...@@ -141,12 +147,13 @@ void main() { ...@@ -141,12 +147,13 @@ void main() {
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' └─child 4: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n' ' └─child 4: RenderSliverToBoxAdapter#00000 relayoutBoundary=up1 NEEDS-PAINT\n'
' │ parentData: paintOffset=Offset(0.0, 600.0) (can use size)\n' ' │ parentData: paintOffset=Offset(0.0, 1600.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 0.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 0.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n' ' │ geometry: SliverGeometry(scrollExtent: 400.0, hidden,\n'
' │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n' ' │ maxPaintExtent: 400.0, hasVisualOverflow: true)\n'
' │\n' ' │\n'
...@@ -158,23 +165,23 @@ void main() { ...@@ -158,23 +165,23 @@ void main() {
); );
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 800.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1200.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1600.0));
expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0)); expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0));
expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0)); expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0));
expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1200.0));
expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1600.0));
expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 2000.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1400.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -182,7 +189,7 @@ void main() { ...@@ -182,7 +189,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -190,7 +197,7 @@ void main() { ...@@ -190,7 +197,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
final HitTestResult result = new HitTestResult(); final HitTestResult result = new HitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0)); root.hitTest(result, position: const Offset(130.0, 150.0));
...@@ -218,17 +225,17 @@ void main() { ...@@ -218,17 +225,17 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -600.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1000.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1400.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1200.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -236,7 +243,7 @@ void main() { ...@@ -236,7 +243,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -244,7 +251,7 @@ void main() { ...@@ -244,7 +251,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
final HitTestResult result = new HitTestResult(); final HitTestResult result = new HitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0)); root.hitTest(result, position: const Offset(150.0, 350.0));
...@@ -284,28 +291,28 @@ void main() { ...@@ -284,28 +291,28 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1200.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1600.0, 0.0));
expect(_getPaintOrigin(sliverA), const Offset(0.0, 0.0)); expect(_getPaintOrigin(sliverA), const Offset(0.0, 0.0));
expect(_getPaintOrigin(sliverB), const Offset(400.0, 0.0)); expect(_getPaintOrigin(sliverB), const Offset(400.0, 0.0));
expect(_getPaintOrigin(sliverC), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverC), const Offset(800.0, 0.0));
expect(_getPaintOrigin(sliverD), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverD), const Offset(1200.0, 0.0));
expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverE), const Offset(1600.0, 0.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1400.0, 0.0));
expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0)); expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0));
expect(_getPaintOrigin(sliverB), const Offset(200.0, 0.0)); expect(_getPaintOrigin(sliverB), const Offset(200.0, 0.0));
expect(_getPaintOrigin(sliverC), const Offset(600.0, 0.0)); expect(_getPaintOrigin(sliverC), const Offset(600.0, 0.0));
expect(_getPaintOrigin(sliverD), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverD), const Offset(1000.0, 0.0));
expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverE), const Offset(1400.0, 0.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -313,13 +320,13 @@ void main() { ...@@ -313,13 +320,13 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0)); expect(_getPaintOrigin(sliverA), const Offset(000.0, 0.0));
expect(_getPaintOrigin(sliverB), const Offset(000.0, 0.0)); expect(_getPaintOrigin(sliverB), const Offset(000.0, 0.0));
expect(_getPaintOrigin(sliverC), const Offset(200.0, 0.0)); expect(_getPaintOrigin(sliverC), const Offset(200.0, 0.0));
expect(_getPaintOrigin(sliverD), const Offset(600.0, 0.0)); expect(_getPaintOrigin(sliverD), const Offset(600.0, 0.0));
expect(_getPaintOrigin(sliverE), const Offset(800.0, 0.0)); expect(_getPaintOrigin(sliverE), const Offset(1000.0, 0.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -362,16 +369,16 @@ void main() { ...@@ -362,16 +369,16 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-800.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1200.0, 0.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1000.0, 0.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -379,7 +386,7 @@ void main() { ...@@ -379,7 +386,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -429,23 +436,23 @@ void main() { ...@@ -429,23 +436,23 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 800.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1200.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1600.0));
expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0)); expect(a.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 400.0));
expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0)); expect(b.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 800.0));
expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(c.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1200.0));
expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(d.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1600.0));
expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 1000.0)); expect(e.localToGlobal(const Offset(800.0, 400.0)), const Offset(800.0, 2000.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1400.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -453,7 +460,7 @@ void main() { ...@@ -453,7 +460,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 1000.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -461,7 +468,7 @@ void main() { ...@@ -461,7 +468,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 600.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
final HitTestResult result = new HitTestResult(); final HitTestResult result = new HitTestResult();
root.hitTest(result, position: const Offset(130.0, 150.0)); root.hitTest(result, position: const Offset(130.0, 150.0));
...@@ -489,17 +496,17 @@ void main() { ...@@ -489,17 +496,17 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -600.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1000.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1400.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -1200.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -507,7 +514,7 @@ void main() { ...@@ -507,7 +514,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -800.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -515,7 +522,7 @@ void main() { ...@@ -515,7 +522,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 700.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -400.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -500.0));
final HitTestResult result = new HitTestResult(); final HitTestResult result = new HitTestResult();
root.hitTest(result, position: const Offset(150.0, 350.0)); root.hitTest(result, position: const Offset(150.0, 350.0));
...@@ -544,16 +551,16 @@ void main() { ...@@ -544,16 +551,16 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1200.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1600.0, 0.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1400.0, 0.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -561,7 +568,7 @@ void main() { ...@@ -561,7 +568,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(800.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(1000.0, 0.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -598,16 +605,16 @@ void main() { ...@@ -598,16 +605,16 @@ void main() {
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(400.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-800.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1200.0, 0.0));
root.offset = new ViewportOffset.fixed(200.0); root.offset = new ViewportOffset.fixed(200.0);
pumpFrame(); pumpFrame();
expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-1000.0, 0.0));
root.offset = new ViewportOffset.fixed(600.0); root.offset = new ViewportOffset.fixed(600.0);
pumpFrame(); pumpFrame();
...@@ -615,7 +622,7 @@ void main() { ...@@ -615,7 +622,7 @@ void main() {
expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0)); expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0)); expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 0.0));
expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0)); expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(-200.0, 0.0));
expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-400.0, 0.0)); expect(e.localToGlobal(const Offset(0.0, 0.0)), const Offset(-600.0, 0.0));
root.offset = new ViewportOffset.fixed(900.0); root.offset = new ViewportOffset.fixed(900.0);
pumpFrame(); pumpFrame();
...@@ -684,7 +691,7 @@ void main() { ...@@ -684,7 +691,7 @@ void main() {
visible: true, visible: true,
).toString(), ).toString(),
equals( equals(
'SliverGeometry(scrollExtent: 100.0, paintExtent: 50.0, layoutExtent: 20.0, maxPaintExtent: 0.0)', 'SliverGeometry(scrollExtent: 100.0, paintExtent: 50.0, layoutExtent: 20.0, maxPaintExtent: 0.0, cacheExtent: 20.0)',
), ),
); );
expect( expect(
...@@ -694,7 +701,7 @@ void main() { ...@@ -694,7 +701,7 @@ void main() {
layoutExtent: 20.0, layoutExtent: 20.0,
).toString(), ).toString(),
equals( equals(
'SliverGeometry(scrollExtent: 100.0, hidden, layoutExtent: 20.0, maxPaintExtent: 0.0)', 'SliverGeometry(scrollExtent: 100.0, hidden, layoutExtent: 20.0, maxPaintExtent: 0.0, cacheExtent: 20.0)',
), ),
); );
}); });
......
...@@ -60,9 +60,9 @@ void main() { ...@@ -60,9 +60,9 @@ void main() {
), ),
); );
double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index))).height; double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index), skipOffstage: false)).height;
double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index))).dy; double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index))).dy; double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index), skipOffstage: false)).dy;
listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100)); listKey.currentState.insertItem(0, duration: const Duration(milliseconds: 100));
await tester.pump(); await tester.pump();
......
...@@ -71,41 +71,43 @@ void tests({ @required bool impliedMode }) { ...@@ -71,41 +71,43 @@ void tests({ @required bool impliedMode }) {
addAutomaticKeepAlives: impliedMode, addAutomaticKeepAlives: impliedMode,
addRepaintBoundaries: impliedMode, addRepaintBoundaries: impliedMode,
itemExtent: 12.3, // about 50 widgets visible itemExtent: 12.3, // about 50 widgets visible
cacheExtent: 0.0,
children: generateList(const Placeholder(), impliedMode: impliedMode), children: generateList(const Placeholder(), impliedMode: impliedMode),
), ),
), ),
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
testWidgets('AutomaticKeepAlive with ListView without itemExtent', (WidgetTester tester) async { testWidgets('AutomaticKeepAlive with ListView without itemExtent', (WidgetTester tester) async {
...@@ -115,6 +117,7 @@ void tests({ @required bool impliedMode }) { ...@@ -115,6 +117,7 @@ void tests({ @required bool impliedMode }) {
child: new ListView( child: new ListView(
addAutomaticKeepAlives: impliedMode, addAutomaticKeepAlives: impliedMode,
addRepaintBoundaries: impliedMode, addRepaintBoundaries: impliedMode,
cacheExtent: 0.0,
children: generateList( children: generateList(
new Container(height: 12.3, child: const Placeholder()), // about 50 widgets visible new Container(height: 12.3, child: const Placeholder()), // about 50 widgets visible
impliedMode: impliedMode, impliedMode: impliedMode,
...@@ -124,35 +127,36 @@ void tests({ @required bool impliedMode }) { ...@@ -124,35 +127,36 @@ void tests({ @required bool impliedMode }) {
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
testWidgets('AutomaticKeepAlive with GridView', (WidgetTester tester) async { testWidgets('AutomaticKeepAlive with GridView', (WidgetTester tester) async {
...@@ -164,6 +168,7 @@ void tests({ @required bool impliedMode }) { ...@@ -164,6 +168,7 @@ void tests({ @required bool impliedMode }) {
addRepaintBoundaries: impliedMode, addRepaintBoundaries: impliedMode,
crossAxisCount: 2, crossAxisCount: 2,
childAspectRatio: 400.0 / 24.6, // about 50 widgets visible childAspectRatio: 400.0 / 24.6, // about 50 widgets visible
cacheExtent: 0.0,
children: generateList( children: generateList(
new Container(child: const Placeholder()), new Container(child: const Placeholder()),
impliedMode: impliedMode, impliedMode: impliedMode,
...@@ -173,35 +178,36 @@ void tests({ @required bool impliedMode }) { ...@@ -173,35 +178,36 @@ void tests({ @required bool impliedMode }) {
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
} }
...@@ -216,6 +222,7 @@ void main() { ...@@ -216,6 +222,7 @@ void main() {
child: new ListView( child: new ListView(
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
...@@ -245,11 +252,11 @@ void main() { ...@@ -245,11 +252,11 @@ void main() {
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); // move to top await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); // move to top
...@@ -257,41 +264,48 @@ void main() { ...@@ -257,41 +264,48 @@ void main() {
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(true);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(1).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
}); });
testWidgets('AutomaticKeepAlive double', (WidgetTester tester) async { testWidgets('AutomaticKeepAlive double 2', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
...@@ -328,13 +342,15 @@ void main() { ...@@ -328,13 +342,15 @@ void main() {
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
...@@ -344,6 +360,7 @@ void main() { ...@@ -344,6 +360,7 @@ void main() {
child: new ListView( child: new ListView(
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
...@@ -376,7 +393,7 @@ void main() { ...@@ -376,7 +393,7 @@ void main() {
), ),
)); ));
await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up. await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
...@@ -387,22 +404,26 @@ void main() { ...@@ -387,22 +404,26 @@ void main() {
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
...@@ -437,10 +458,10 @@ void main() { ...@@ -437,10 +458,10 @@ void main() {
await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up. await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
}); });
testWidgets('AutomaticKeepAlive with keepAlive set to true before initState', (WidgetTester tester) async { testWidgets('AutomaticKeepAlive with keepAlive set to true before initState', (WidgetTester tester) async {
...@@ -469,9 +490,9 @@ void main() { ...@@ -469,9 +490,9 @@ void main() {
expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0), skipOffstage: false), findsOneWidget);
expect(find.text('keep me alive'), findsOneWidget); expect(find.text('keep me alive', skipOffstage: false), findsOneWidget);
expect(find.text('FooBar 1'), findsNothing); expect(find.text('FooBar 1'), findsNothing);
expect(find.text('FooBar 2'), findsNothing); expect(find.text('FooBar 2'), findsNothing);
}); });
......
...@@ -168,9 +168,17 @@ void main() { ...@@ -168,9 +168,17 @@ void main() {
0, 1, 2, // col 0 0, 1, 2, // col 0
3, 4, 5, // col 1 3, 4, 5, // col 1
6, 7, 8, // col 2 6, 7, 8, // col 2
9, 10, 11, // col 3 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 9; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 9; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
final ScrollableState state = tester.state(find.byType(Scrollable)); final ScrollableState state = tester.state(find.byType(Scrollable));
final ScrollPosition position = state.position; final ScrollPosition position = state.position;
position.jumpTo(3025.0); position.jumpTo(3025.0);
...@@ -179,25 +187,49 @@ void main() { ...@@ -179,25 +187,49 @@ void main() {
await tester.pump(); await tester.pump();
expect(log, equals(<int>[ expect(log, equals(<int>[
30, 31, 32, // col 10 (in cached area)
33, 34, 35, // col 11 33, 34, 35, // col 11
36, 37, 38, // col 12 36, 37, 38, // col 12
39, 40, 41, // col 13 39, 40, 41, // col 13
42, 43, 44, // col 14 42, 43, 44, // col 14
45, 46, 47, // col 15 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 33; i++) {
expect(find.text('$i'), findsNothing);
}
for (int i = 33; i < 45; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 45; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
position.jumpTo(975.0); position.jumpTo(975.0);
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[ expect(log, equals(<int>[
6, 7, 8, // col2 (in cached area)
9, 10, 11, // col 3 9, 10, 11, // col 3
12, 13, 14, // col 4 12, 13, 14, // col 4
15, 16, 17, // col 5 15, 16, 17, // col 5
18, 19, 20, // col 6 18, 19, 20, // col 6
21, 22, 23, // col 7 (in cached area)
])); ]));
log.clear(); log.clear();
for (int i = 0; i < 9; i++) {
expect(find.text('$i'), findsNothing);
}
for (int i = 9; i < 21; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 21; i < 80; i++) {
expect(find.text('$i'), findsNothing);
}
}); });
testWidgets('GridView - change crossAxisCount', (WidgetTester tester) async { testWidgets('GridView - change crossAxisCount', (WidgetTester tester) async {
...@@ -230,7 +262,15 @@ void main() { ...@@ -230,7 +262,15 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
for (int i = 0; i < 12; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 12; i < 40; i++) {
expect(find.text('$i'), findsNothing);
}
log.clear(); log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -258,6 +298,8 @@ void main() { ...@@ -258,6 +298,8 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
log.clear(); log.clear();
...@@ -295,7 +337,15 @@ void main() { ...@@ -295,7 +337,15 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
for (int i = 0; i < 12; i++) {
expect(find.text('$i'), findsOneWidget);
}
for (int i = 12; i < 40; i++) {
expect(find.text('$i'), findsNothing);
}
log.clear(); log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -323,6 +373,8 @@ void main() { ...@@ -323,6 +373,8 @@ void main() {
0, 1, 2, 3, // row 0 0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1 4, 5, 6, 7, // row 1
8, 9, 10, 11, // row 2 8, 9, 10, 11, // row 2
12, 13, 14, 15, // row 3 (in cached area)
16, 17, 18, 19, // row 4 (in cached area)
])); ]));
log.clear(); log.clear();
...@@ -346,6 +398,7 @@ void main() { ...@@ -346,6 +398,7 @@ void main() {
child: new SizedBox( child: new SizedBox(
height: 200.0, height: 200.0,
child: new GridView.count( child: new GridView.count(
cacheExtent: 0.0,
crossAxisCount: 2, crossAxisCount: 2,
children: <Widget>[ container, container, container, container ], children: <Widget>[ container, container, container, container ],
), ),
...@@ -379,7 +432,7 @@ void main() { ...@@ -379,7 +432,7 @@ void main() {
), ),
); );
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsNothing);
expect(find.text('1'), findsNothing); expect(find.text('1'), findsNothing);
}); });
......
...@@ -781,6 +781,7 @@ void main() { ...@@ -781,6 +781,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
return new Material( return new Material(
child: new ListView( child: new ListView(
cacheExtent: 0.0,
children: <Widget>[ children: <Widget>[
const SizedBox(height: 100.0), const SizedBox(height: 100.0),
// This container will appear at Y=100 // This container will appear at Y=100
......
...@@ -47,6 +47,7 @@ void main() { ...@@ -47,6 +47,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
itemExtent: 12.3, // about 50 widgets visible itemExtent: 12.3, // about 50 widgets visible
...@@ -56,35 +57,36 @@ void main() { ...@@ -56,35 +57,36 @@ void main() {
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
testWidgets('KeepAlive with ListView without itemExtent', (WidgetTester tester) async { testWidgets('KeepAlive with ListView without itemExtent', (WidgetTester tester) async {
...@@ -92,6 +94,7 @@ void main() { ...@@ -92,6 +94,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
children: generateList(new Container(height: 12.3, child: const Placeholder())), // about 50 widgets visible children: generateList(new Container(height: 12.3, child: const Placeholder())), // about 50 widgets visible
...@@ -100,35 +103,36 @@ void main() { ...@@ -100,35 +103,36 @@ void main() {
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
testWidgets('KeepAlive with GridView', (WidgetTester tester) async { testWidgets('KeepAlive with GridView', (WidgetTester tester) async {
...@@ -136,6 +140,7 @@ void main() { ...@@ -136,6 +140,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new GridView.count( child: new GridView.count(
cacheExtent: 0.0,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
crossAxisCount: 2, crossAxisCount: 2,
...@@ -146,35 +151,36 @@ void main() { ...@@ -146,35 +151,36 @@ void main() {
); );
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(true);
await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false); const GlobalObjectKey<_LeafState>(60).currentState.setKeepAlive(false);
await tester.pump(); await tester.pump();
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget); expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(90)), findsNothing); expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
}); });
testWidgets('KeepAlive render tree description', (WidgetTester tester) async { testWidgets('KeepAlive render tree description', (WidgetTester tester) async {
...@@ -270,10 +276,12 @@ void main() { ...@@ -270,10 +276,12 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ currently live children: 0 to 1\n' ' │ cacheExtent: 850.0)\n'
' │ currently live children: 0 to 2\n'
' │\n' ' │\n'
' ├─child with index 0: RenderLimitedBox#00000\n' ' ├─child with index 0: RenderLimitedBox#00000\n'
' │ │ parentData: index=0; layoutOffset=0.0\n' ' │ │ parentData: index=0; layoutOffset=0.0\n'
...@@ -287,8 +295,20 @@ void main() { ...@@ -287,8 +295,20 @@ void main() {
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' └─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here ' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
' │ parentData: index=1; layoutOffset=400.0\n' ' │ │ parentData: index=1; layoutOffset=400.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' └─child with index 2: RenderLimitedBox#00000\n'
' │ parentData: index=2; layoutOffset=800.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n' ' │ maxWidth: 400.0\n'
...@@ -385,10 +405,24 @@ void main() { ...@@ -385,10 +405,24 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 1100.0\n'
' │ cacheOrigin: -250.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ currently live children: 5 to 6\n' ' │ cacheExtent: 1100.0)\n'
' │ currently live children: 4 to 7\n'
' │\n'
' ├─child with index 4: RenderLimitedBox#00000\n'
' │ │ parentData: index=4; layoutOffset=1600.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0 ' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
' │ │ parentData: index=5; layoutOffset=2000.0\n' ' │ │ parentData: index=5; layoutOffset=2000.0\n'
...@@ -403,7 +437,19 @@ void main() { ...@@ -403,7 +437,19 @@ void main() {
' │ size: Size(800.0, 400.0)\n' ' │ size: Size(800.0, 400.0)\n'
' │\n' ' │\n'
' ├─child with index 6: RenderLimitedBox#00000\n' ' ├─child with index 6: RenderLimitedBox#00000\n'
' ╎ │ parentData: index=6; layoutOffset=2400.0\n' ' │ │ parentData: index=6; layoutOffset=2400.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' ├─child with index 7: RenderLimitedBox#00000\n'
' ╎ │ parentData: index=7; layoutOffset=2800.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n' ' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n' ' ╎ │ maxWidth: 400.0\n'
......
...@@ -38,7 +38,11 @@ void main() { ...@@ -38,7 +38,11 @@ void main() {
final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget)); final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible in viewport
6, 7, 8, // in caching area
]));
check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
callbackTracker.clear(); callbackTracker.clear();
testWidget.flip(); testWidget.flip();
...@@ -50,7 +54,11 @@ void main() { ...@@ -50,7 +54,11 @@ void main() {
testWidget.flip(); testWidget.flip();
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5,
6, 7, 8, // in caching area
]));
check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
}); });
testWidgets('ListView.builder vertical', (WidgetTester tester) async { testWidgets('ListView.builder vertical', (WidgetTester tester) async {
...@@ -91,7 +99,12 @@ void main() { ...@@ -91,7 +99,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, // in caching area
1, 2, 3, 4,
5, // in caching area
]));
check(visible: <int>[1, 2, 3, 4], hidden: <int>[0, 5]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(400.0); jumpTo(400.0);
...@@ -99,12 +112,12 @@ void main() { ...@@ -99,12 +112,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
callbackTracker.clear(); 0, 1, // in caching area
2, 3, 4,
await tester.pumpWidget(buildWidget()); 5, 6, // in caching area
]));
expect(callbackTracker, equals(<int>[2, 3, 4])); check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5, 6]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(500.0); jumpTo(500.0);
...@@ -112,7 +125,12 @@ void main() { ...@@ -112,7 +125,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, // in caching area
2, 3, 4, 5,
6, // in caching area
]));
check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6]);
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -155,8 +173,12 @@ void main() { ...@@ -155,8 +173,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, // in caching area
1, 2, 3, 4, 5,
6, // in caching area
]));
check(visible: <int>[1, 2, 3, 4, 5], hidden: <int>[0, 6]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(400.0); jumpTo(400.0);
...@@ -164,12 +186,12 @@ void main() { ...@@ -164,12 +186,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
callbackTracker.clear(); 0, 1, // in caching area
2, 3, 4, 5,
await tester.pumpWidget(buildWidget()); 6, 7, // in caching area
]));
expect(callbackTracker, equals(<int>[2, 3, 4, 5])); check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6, 7]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(500.0); jumpTo(500.0);
...@@ -177,7 +199,12 @@ void main() { ...@@ -177,7 +199,12 @@ void main() {
await tester.pumpWidget(buildWidget()); await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[2, 3, 4, 5, 6])); expect(callbackTracker, equals(<int>[
0, 1, // in caching area
2, 3, 4, 5, 6,
7, // in caching area
]));
check(visible: <int>[2, 3, 4, 5, 6], hidden: <int>[0, 1, 7]);
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -208,26 +235,39 @@ void main() { ...@@ -208,26 +235,39 @@ void main() {
} }
await tester.pumpWidget(testWidget); await tester.pumpWidget(testWidget);
expect(callbackTracker, equals(<int>[0, 1])); expect(callbackTracker, equals(<int>[0, 1, 2]));
check(visible: <int>[0, 1], hidden: <int>[2]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(150.0); jumpTo(150.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[2])); expect(callbackTracker, equals(<int>[3]));
check(visible: <int>[0, 1, 2], hidden: <int>[3]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(600.0); jumpTo(600.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[3])); expect(callbackTracker, equals(<int>[4]));
check(visible: <int>[2, 3], hidden: <int>[0, 1, 4]);
callbackTracker.clear(); callbackTracker.clear();
jumpTo(750.0); jumpTo(750.0);
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[4])); expect(callbackTracker, equals(<int>[5]));
check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5]);
callbackTracker.clear(); callbackTracker.clear();
}); });
} }
void check({List<int> visible: const <int>[], List<int> hidden: const <int>[]}) {
for (int i in visible) {
expect(find.text('$i'), findsOneWidget);
}
for (int i in hidden) {
expect(find.text('$i'), findsNothing);
}
}
...@@ -12,6 +12,7 @@ void main() { ...@@ -12,6 +12,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
new Container(height: 400.0, child: const Text('1')), new Container(height: 400.0, child: const Text('1')),
...@@ -34,6 +35,7 @@ void main() { ...@@ -34,6 +35,7 @@ void main() {
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new ListView( child: new ListView(
cacheExtent: 0.0,
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
new Container(height: 200.0, child: const Text('1')), new Container(height: 200.0, child: const Text('1')),
...@@ -53,16 +55,73 @@ void main() { ...@@ -53,16 +55,73 @@ void main() {
controller.jumpTo(300.0); controller.jumpTo(300.0);
await tester.pump(); await tester.pump();
expect(controller.offset, equals(300.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(100.0)); expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));
controller.jumpTo(50.0); controller.jumpTo(50.0);
await tester.pump(); await tester.pump();
expect(controller.offset, equals(0.0)); expect(controller.offset, equals(0.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(200.0)); expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
}); });
testWidgets('ListView can handle shrinking top elements with cache extent', (WidgetTester tester) async {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
controller: controller,
children: <Widget>[
new Container(height: 400.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
new Container(height: 400.0, child: const Text('4')),
new Container(height: 400.0, child: const Text('5')),
new Container(height: 400.0, child: const Text('6')),
],
),
),
);
controller.jumpTo(1000.0);
await tester.pump();
expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
controller: controller,
children: <Widget>[
new Container(height: 200.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
new Container(height: 400.0, child: const Text('4')),
new Container(height: 400.0, child: const Text('5')),
new Container(height: 400.0, child: const Text('6')),
],
),
),
);
expect(controller.offset, equals(1000.0));
expect(tester.getTopLeft(find.text('4')).dy, equals(200.0));
controller.jumpTo(300.0);
await tester.pump();
expect(controller.offset, equals(250.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(-50.0));
controller.jumpTo(50.0);
await tester.pump();
expect(controller.offset, equals(50.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(150.0));
});
testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async { testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async {
final ScrollController controller = new ScrollController(); final ScrollController controller = new ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -150,7 +150,7 @@ void main() { ...@@ -150,7 +150,7 @@ void main() {
), ),
); );
final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList)); final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList, skipOffstage: false));
final double maxScrollOffset = element.estimateMaxScrollOffset( final double maxScrollOffset = element.estimateMaxScrollOffset(
null, null,
......
...@@ -96,7 +96,7 @@ void main() { ...@@ -96,7 +96,7 @@ void main() {
), ),
); );
expect(log, equals(<int>[0, 1, 2])); expect(log, equals(<int>[0, 1, 2, 3, 4]));
log.clear(); log.clear();
final ScrollableState state = tester.state(find.byType(Scrollable)); final ScrollableState state = tester.state(find.byType(Scrollable));
...@@ -106,7 +106,7 @@ void main() { ...@@ -106,7 +106,7 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[10, 11, 12, 13])); expect(log, equals(<int>[8, 9, 10, 11, 12, 13, 14]));
log.clear(); log.clear();
position.jumpTo(975.0); position.jumpTo(975.0);
...@@ -114,7 +114,7 @@ void main() { ...@@ -114,7 +114,7 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
await tester.pump(); await tester.pump();
expect(log, equals(<int>[4, 5, 6, 7])); expect(log, equals(<int>[7, 6, 5, 4, 3]));
log.clear(); log.clear();
}); });
...@@ -196,7 +196,7 @@ void main() { ...@@ -196,7 +196,7 @@ void main() {
), ),
), ),
); );
expect(find.text('padded'), findsOneWidget); expect(find.text('padded', skipOffstage: false), findsOneWidget);
}); });
testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async { testWidgets('ListView with itemExtent in unbounded context', (WidgetTester tester) async {
...@@ -240,7 +240,7 @@ void main() { ...@@ -240,7 +240,7 @@ void main() {
), ),
); );
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=5'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=7']));
delegate.log.clear(); delegate.log.clear();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -253,7 +253,7 @@ void main() { ...@@ -253,7 +253,7 @@ void main() {
), ),
); );
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=2'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=4']));
delegate.log.clear(); delegate.log.clear();
await tester.drag(find.byType(ListView), const Offset(0.0, -600.0)); await tester.drag(find.byType(ListView), const Offset(0.0, -600.0));
...@@ -262,7 +262,7 @@ void main() { ...@@ -262,7 +262,7 @@ void main() {
await tester.pump(); await tester.pump();
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=2 lastIndex=5'])); expect(delegate.log, equals(<String>['didFinishLayout firstIndex=1 lastIndex=6']));
delegate.log.clear(); delegate.log.clear();
}); });
......
...@@ -38,7 +38,10 @@ void main() { ...@@ -38,7 +38,10 @@ void main() {
final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget)); final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8 // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
testWidget.flip(); testWidget.flip();
...@@ -50,7 +53,10 @@ void main() { ...@@ -50,7 +53,10 @@ void main() {
testWidget.flip(); testWidget.flip();
await tester.pump(); await tester.pump();
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4, 5, // visible
6, 7, 8, // in cached area
]));
}); });
testWidgets('ListView vertical', (WidgetTester tester) async { testWidgets('ListView vertical', (WidgetTester tester) async {
...@@ -86,22 +92,34 @@ void main() { ...@@ -86,22 +92,34 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 is built to find its height // 0 is built to find its height
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, 1, 2, 3, 4,
5, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable)); final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(400.0); // now only 3 should fit, numbered 2-4. scrollable.position.jumpTo(600.0); // now only 3 should fit, numbered 3-5.
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// We build the visible children to find their new size. // We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, 4, 5, //visible
6, 7
]));
callbackTracker.clear(); callbackTracker.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 isn't built because they're not visible. // 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[1, 2, 3, 4])); expect(callbackTracker, equals(<int>[
1, 2,
3, 4, 5, // visible
6, 7,
]
));
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -128,7 +146,7 @@ void main() { ...@@ -128,7 +146,7 @@ void main() {
child: new FlipWidget( child: new FlipWidget(
left: new ListView.builder( left: new ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: new ScrollController(initialScrollOffset: 300.0), controller: new ScrollController(initialScrollOffset: 500.0),
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
), ),
right: const Text('Not Today'), right: const Text('Not Today'),
...@@ -139,23 +157,23 @@ void main() { ...@@ -139,23 +157,23 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 is built to find its width // 0 is built to find its width
expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[0, 1, 2, 3, 4, 5, 6, 7]));
callbackTracker.clear(); callbackTracker.clear();
final ScrollableState scrollable = tester.state(find.byType(Scrollable)); final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(400.0); // now only 4 should fit, numbered 2-5. scrollable.position.jumpTo(600.0); // now only 4 should fit, numbered 2-5.
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// We build the visible children to find their new size. // We build the visible children to find their new size.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear(); callbackTracker.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
// 0 isn't built because they're not visible. // 0 isn't built because they're not visible.
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5])); expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5, 6, 7, 8]));
callbackTracker.clear(); callbackTracker.clear();
}); });
...@@ -189,18 +207,24 @@ void main() { ...@@ -189,18 +207,24 @@ void main() {
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
tester.allWidgets.forEach(collectText); tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2'])); expect(text, equals(<String>['0', '1', '2', '3']));
text.clear(); text.clear();
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2])); expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear(); callbackTracker.clear();
tester.allWidgets.forEach(collectText); tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2'])); expect(text, equals(<String>['0', '1', '2', '3']));
text.clear(); text.clear();
}); });
...@@ -308,9 +332,10 @@ void main() { ...@@ -308,9 +332,10 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n' ' │ geometry: SliverGeometry(scrollExtent: 300.0, paintExtent: 300.0,\n'
' │ maxPaintExtent: 300.0)\n' ' │ maxPaintExtent: 300.0, cacheExtent: 300.0)\n'
' │ currently live children: 0 to 2\n' ' │ currently live children: 0 to 2\n'
' │\n' ' │\n'
' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
......
...@@ -339,7 +339,7 @@ void main() { ...@@ -339,7 +339,7 @@ void main() {
final Offset point1 = tester.getCenter(find.text('AA')); final Offset point1 = tester.getCenter(find.text('AA'));
await tester.dragFrom(point1, const Offset(0.0, 200.0)); await tester.dragFrom(point1, const Offset(0.0, 200.0));
await tester.pump(const Duration(milliseconds: 20)); await tester.pump(const Duration(milliseconds: 20));
final Offset point2 = tester.getCenter(find.text('AA')); final Offset point2 = tester.getCenter(find.text('AA', skipOffstage: false));
expect(point1.dy, greaterThan(point2.dy)); expect(point1.dy, greaterThan(point2.dy));
}); });
...@@ -646,4 +646,4 @@ class TestHeader extends SliverPersistentHeaderDelegate { ...@@ -646,4 +646,4 @@ class TestHeader extends SliverPersistentHeaderDelegate {
} }
@override @override
bool shouldRebuild(TestHeader oldDelegate) => false; bool shouldRebuild(TestHeader oldDelegate) => false;
} }
\ No newline at end of file
...@@ -257,7 +257,7 @@ void main() { ...@@ -257,7 +257,7 @@ void main() {
), ),
)); ));
expect(find.text('Alabama'), findsOneWidget); expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -362,6 +364,12 @@ void main() { ...@@ -362,6 +364,12 @@ void main() {
label: r'item 1', label: r'item 1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'item 2',
),
], ],
), ),
], ],
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/rendering.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Traversal Order of SliverList', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
return new Container(
height: 200.0,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Semantics(
container: true,
child: new Text('Item ${i}a'),
),
new Semantics(
container: true,
child: new Text('item ${i}b'),
),
],
),
);
});
await tester.pumpWidget(
new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new CustomScrollView(
controller: new ScrollController(initialScrollOffset: 3000.0),
slivers: <Widget>[
new SliverList(
delegate: new SliverChildListDelegate(listChildren),
),
],
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 13a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 13b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 14a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 14b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 15a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 15b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 16a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 16b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 17a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 17b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 18a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 18b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 19a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 19b',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
childOrder: DebugSemanticsDumpOrder.traversalOrder,
ignoreId: true,
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('Traversal Order of SliverFixedExtentList', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
return new Container(
height: 200.0,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Semantics(
container: true,
child: new Text('Item ${i}a'),
),
new Semantics(
container: true,
child: new Text('item ${i}b'),
),
],
),
);
});
await tester.pumpWidget(
new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new CustomScrollView(
controller: new ScrollController(initialScrollOffset: 3000.0),
slivers: <Widget>[
new SliverFixedExtentList(
itemExtent: 200.0,
delegate: new SliverChildListDelegate(listChildren),
),
],
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 13a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 13b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 14a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 14b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 15a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 15b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 16a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 16b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 17a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 17b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 18a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 18b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 19a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 19b',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
childOrder: DebugSemanticsDumpOrder.traversalOrder,
ignoreId: true,
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('Traversal Order of SliverGrid', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
return new Container(
height: 200.0,
child: new Text('Item $i'),
);
});
await tester.pumpWidget(
new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new CustomScrollView(
controller: new ScrollController(initialScrollOffset: 1600.0),
slivers: <Widget>[
new SliverGrid.count(
crossAxisCount: 2,
crossAxisSpacing: 400.0,
children: listChildren,
),
],
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[SemanticsAction.scrollUp,
SemanticsAction.scrollDown],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 12',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 13',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 14',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 15',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 16',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 17',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 18',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 19',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 20',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 21',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 22',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 23',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 24',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 25',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
childOrder: DebugSemanticsDumpOrder.traversalOrder,
ignoreId: true,
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('Traversal Order of List of individual slivers', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
return new SliverToBoxAdapter(
child: new Container(
height: 200.0,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Semantics(
container: true,
child: new Text('Item ${i}a'),
),
new Semantics(
container: true,
child: new Text('item ${i}b'),
),
],
),
),
);
});
await tester.pumpWidget(
new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new CustomScrollView(
controller: new ScrollController(initialScrollOffset: 3000.0),
slivers: listChildren,
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 13a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 13b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 14a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 14b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 15a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 15b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 16a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 16b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 17a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 17b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 18a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 18b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 19a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 19b',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
childOrder: DebugSemanticsDumpOrder.traversalOrder,
ignoreId: true,
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('Traversal Order of in a SingleChildScrollView', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
return new Container(
height: 200.0,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Semantics(
container: true,
child: new Text('Item ${i}a'),
),
new Semantics(
container: true,
child: new Text('item ${i}b'),
),
],
),
);
});
await tester.pumpWidget(
new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new SingleChildScrollView(
controller: new ScrollController(initialScrollOffset: 3000.0),
child: new Column(
children: listChildren,
),
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 13a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 13b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 14a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 14b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 15a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 15b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 16a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 16b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 17a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'item 17b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 18a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 18b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 19a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'item 19b',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
childOrder: DebugSemanticsDumpOrder.traversalOrder,
ignoreId: true,
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('Traversal Order with center child', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scrollable(
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return new Viewport(
offset: offset,
center: const ValueKey<int>(0),
slivers: new List<Widget>.generate(30, (int i) {
final int item = i - 15;
return new SliverToBoxAdapter(
key: new ValueKey<int>(item),
child: new Container(
height: 200.0,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Semantics(
container: true,
child: new Text('${item}a'),
),
new Semantics(
container: true,
child: new Text('${item}b'),
),
],
),
),
);
}),
);
},
),
),
),
));
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '-2a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '-2b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '-1a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '-1b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '0a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '0b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '1a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '1b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '2a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: '2b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '3a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '3b',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '4a',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: '4b',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
ignoreId: true,
));
semantics.dispose();
});
}
...@@ -2,9 +2,13 @@ ...@@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
class TestScrollPosition extends ScrollPositionWithSingleContext { class TestScrollPosition extends ScrollPositionWithSingleContext {
TestScrollPosition({ TestScrollPosition({
ScrollPhysics physics, ScrollPhysics physics,
...@@ -170,4 +174,171 @@ void main() { ...@@ -170,4 +174,171 @@ void main() {
); );
expect(innerScrollable.controller, isNull); expect(innerScrollable.controller, isNull);
}); });
testWidgets('SingleChildScrollView semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new SingleChildScrollView(
controller: controller,
child: new Column(
children: new List<Widget>.generate(30, (int i) {
return new Container(
height: 200.0,
child: new Text('Tile $i'),
);
}),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
],
children: <TestSemantics>[
new TestSemantics(
label: r'Tile 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,],
label: r'Tile 4',
textDirection: TextDirection.ltr,
),
],
),
],
),
ignoreRect: true, ignoreTransform: true, ignoreId: true,
));
controller.jumpTo(3000.0);
await tester.pumpAndSettle();
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 13',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 14',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 15',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 16',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 17',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 18',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 19',
textDirection: TextDirection.ltr,
),
],
),
],
),
ignoreRect: true, ignoreTransform: true, ignoreId: true,
));
controller.jumpTo(6000.0);
await tester.pumpAndSettle();
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 25',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'Tile 26',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 27',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 28',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'Tile 29',
textDirection: TextDirection.ltr,
),
],
),
],
),
ignoreRect: true, ignoreTransform: true, ignoreId: true,
));
semantics.dispose();
});
} }
...@@ -70,17 +70,42 @@ void main() { ...@@ -70,17 +70,42 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n' ' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true)\n' ' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
' │ currently live children: 0 to 0\n' ' │ cacheExtent: 850.0)\n'
' │ currently live children: 0 to 1\n'
' │\n' ' │\n'
' └─child with index 0: RenderRepaintBoundary#00000\n' ' ├─child with index 0: RenderRepaintBoundary#00000\n'
' │ parentData: index=0; layoutOffset=0.0\n' ' │ │ parentData: index=0; layoutOffset=0.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ │ layer: OffsetLayer#00000\n'
' │ │ size: Size(800.0, 600.0)\n'
' │ │ metrics: 75.0% useful (1 bad vs 3 good)\n'
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ │ repaints)\n'
' │ │\n'
' │ └─child: RenderParagraph#00000\n'
' │ │ parentData: <none> (can use size)\n'
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ │ size: Size(800.0, 600.0)\n'
' │ │ textAlign: start\n'
' │ │ textDirection: ltr\n'
' │ │ softWrap: wrapping at box width\n'
' │ │ overflow: clip\n'
' │ │ maxLines: unlimited\n'
' │ ╘═╦══ text ═══\n'
' │ ║ TextSpan:\n'
' │ ║ <all styles inherited>\n'
' │ ║ "0"\n'
' │ ╚═══════════\n'
' └─child with index 1: RenderRepaintBoundary#00000\n'
' │ parentData: index=1; layoutOffset=600.0\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n' ' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ metrics: 50.0% useful (1 bad vs 1 good)\n' ' │ metrics: 75.0% useful (1 bad vs 3 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n' ' │ repaints)\n'
' │\n' ' │\n'
...@@ -96,7 +121,7 @@ void main() { ...@@ -96,7 +121,7 @@ void main() {
' ╘═╦══ text ═══\n' ' ╘═╦══ text ═══\n'
' ║ TextSpan:\n' ' ║ TextSpan:\n'
' ║ <all styles inherited>\n' ' ║ <all styles inherited>\n'
' ║ "0"\n' ' ║ "1"\n'
' ╚═══════════\n' ' ╚═══════════\n'
), ),
); );
......
...@@ -131,7 +131,7 @@ void main() { ...@@ -131,7 +131,7 @@ void main() {
); );
// Item 0 exists in the list and as the prototype item. // Item 0 exists in the list and as the prototype item.
expect(tester.widgetList(find.text('Item 0')).length, 2); expect(tester.widgetList(find.text('Item 0', skipOffstage: false)).length, 2);
for (int i = 1; i < 10; i += 1) for (int i = 1; i < 10; i += 1)
expect(find.text('Item $i'), findsOneWidget); expect(find.text('Item $i'), findsOneWidget);
......
...@@ -8,7 +8,6 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -8,7 +8,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart';
import 'semantics_tester.dart'; import 'semantics_tester.dart';
...@@ -36,22 +35,25 @@ void _tests() { ...@@ -36,22 +35,25 @@ void _tests() {
); );
}); });
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new MediaQuery(
controller: scrollController, data: const MediaQueryData(),
slivers: <Widget>[ child: new CustomScrollView(
const SliverAppBar( controller: scrollController,
pinned: true, slivers: <Widget>[
expandedHeight: appBarExpandedHeight, const SliverAppBar(
title: const Text('Semantics Test with Slivers'), pinned: true,
), expandedHeight: appBarExpandedHeight,
new SliverList( title: const Text('Semantics Test with Slivers'),
delegate: new SliverChildListDelegate(listChildren), ),
), new SliverList(
], delegate: new SliverChildListDelegate(listChildren),
),
],
),
), ),
), ),
), ),
...@@ -61,29 +63,51 @@ void _tests() { ...@@ -61,29 +63,51 @@ void _tests() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, id: 1,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics], textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 6, id: 2,
actions: <SemanticsAction>[SemanticsAction.scrollUp],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, id: 9,
label: r'Item 0', actions: <SemanticsAction>[SemanticsAction.scrollUp],
textDirection: TextDirection.ltr, children: <TestSemantics>[
), new TestSemantics(
new TestSemantics( id: 7,
id: 3, children: <TestSemantics>[
label: r'Item 1', new TestSemantics(
textDirection: TextDirection.ltr, id: 8,
), flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
new TestSemantics( label: 'Semantics Test with Slivers',
id: 4, textDirection: TextDirection.ltr,
flags: <SemanticsFlag>[SemanticsFlag.namesRoute], ),
label: r'Semantics Test with Slivers', ],
textDirection: TextDirection.ltr, ),
new TestSemantics(
id: 3,
label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 4,
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 5,
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 6,
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
...@@ -103,37 +127,63 @@ void _tests() { ...@@ -103,37 +127,63 @@ void _tests() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, id: 1,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics], textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 6, id: 2,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics(
id: 2,
label: r'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 3,
label: r'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics( new TestSemantics(
id: 7, id: 7,
label: r'Item 2', tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
textDirection: TextDirection.ltr, children: <TestSemantics>[
new TestSemantics(
id: 8,
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'Semantics Test with Slivers',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
id: 9,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
id: 3,
label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 4,
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 5,
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 6,
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 10,
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 4',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
new TestSemantics(
id: 4,
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: r'Semantics Test with Slivers',
textDirection: TextDirection.ltr,
),
], ],
), ),
], ],
...@@ -150,34 +200,53 @@ void _tests() { ...@@ -150,34 +200,53 @@ void _tests() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, id: 1,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics], textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 6, id: 2,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, id: 9,
label: r'Item 0', actions: <SemanticsAction>[
textDirection: TextDirection.ltr, SemanticsAction.scrollUp,
), SemanticsAction.scrollDown,
new TestSemantics( ],
id: 3, children: <TestSemantics>[
label: r'Item 1', new TestSemantics(
textDirection: TextDirection.ltr, id: 7,
), children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7, id: 8,
label: r'Item 2', flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
textDirection: TextDirection.ltr, label: 'Semantics Test with Slivers',
), textDirection: TextDirection.ltr,
new TestSemantics( ),
id: 4, ],
flags: <SemanticsFlag>[SemanticsFlag.namesRoute], ),
label: r'Semantics Test with Slivers', new TestSemantics(
textDirection: TextDirection.ltr, id: 3,
label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 4,
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 5,
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
id: 6,
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
...@@ -192,7 +261,7 @@ void _tests() { ...@@ -192,7 +261,7 @@ void _tests() {
semantics.dispose(); semantics.dispose();
}); });
testWidgets('Offscreen sliver are not included in semantics tree', (WidgetTester tester) async { testWidgets('Offscreen sliver are hidden in semantics tree', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
const double containerHeight = 200.0; const double containerHeight = 200.0;
...@@ -209,14 +278,17 @@ void _tests() { ...@@ -209,14 +278,17 @@ void _tests() {
); );
}); });
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Center( child: new Directionality(
child: new SizedBox( textDirection: TextDirection.ltr,
height: containerHeight, child: new Center(
child: new CustomScrollView( child: new SizedBox(
controller: scrollController, height: containerHeight,
slivers: slivers, child: new CustomScrollView(
controller: scrollController,
slivers: slivers,
),
), ),
), ),
), ),
...@@ -226,23 +298,36 @@ void _tests() { ...@@ -226,23 +298,36 @@ void _tests() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 4,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, actions: <SemanticsAction>[
label: 'Item 2', SemanticsAction.scrollUp,
textDirection: TextDirection.ltr, SemanticsAction.scrollDown,
), ],
new TestSemantics( children: <TestSemantics>[
id: 3, new TestSemantics(
label: 'Item 1', flags: <SemanticsFlag>[SemanticsFlag.isHidden],
textDirection: TextDirection.ltr, label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
...@@ -252,6 +337,7 @@ void _tests() { ...@@ -252,6 +337,7 @@ void _tests() {
), ),
ignoreRect: true, ignoreRect: true,
ignoreTransform: true, ignoreTransform: true,
ignoreId: true,
)); ));
semantics.dispose(); semantics.dispose();
...@@ -269,10 +355,13 @@ void _tests() { ...@@ -269,10 +355,13 @@ void _tests() {
); );
}); });
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new Directionality(
slivers: slivers, textDirection: TextDirection.ltr,
child: new CustomScrollView(
slivers: slivers,
),
), ),
), ),
); );
...@@ -280,37 +369,34 @@ void _tests() { ...@@ -280,37 +369,34 @@ void _tests() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, children: <TestSemantics>[
label: 'Item 4', new TestSemantics(
textDirection: TextDirection.ltr, label: 'Item 4',
), textDirection: TextDirection.ltr,
new TestSemantics( ),
id: 3, new TestSemantics(
label: 'Item 3', label: 'Item 3',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
new TestSemantics( new TestSemantics(
id: 4, label: 'Item 2',
label: 'Item 2', textDirection: TextDirection.ltr,
textDirection: TextDirection.ltr, ),
), new TestSemantics(
new TestSemantics( label: 'Item 1',
id: 5, textDirection: TextDirection.ltr,
label: 'Item 1', ),
textDirection: TextDirection.ltr, new TestSemantics(
), label: 'Item 0',
new TestSemantics( textDirection: TextDirection.ltr,
id: 6, ),
label: 'Item 0', ],
textDirection: TextDirection.ltr,
), ),
], ],
), ),
...@@ -320,6 +406,8 @@ void _tests() { ...@@ -320,6 +406,8 @@ void _tests() {
), ),
ignoreRect: true, ignoreRect: true,
ignoreTransform: true, ignoreTransform: true,
ignoreId: true,
childOrder: DebugSemanticsDumpOrder.inverseHitTest,
)); ));
semantics.dispose(); semantics.dispose();
...@@ -335,89 +423,97 @@ void _tests() { ...@@ -335,89 +423,97 @@ void _tests() {
); );
}); });
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new MediaQuery(
slivers: <Widget>[ data: const MediaQueryData(),
const SliverAppBar( child: new CustomScrollView(
pinned: true, slivers: <Widget>[
expandedHeight: 100.0, const SliverAppBar(
title: const Text('AppBar'), pinned: true,
), expandedHeight: 100.0,
new SliverList( title: const Text('AppBar'),
delegate: new SliverChildListDelegate(listChildren), ),
), new SliverList(
], delegate: new SliverChildListDelegate(listChildren),
controller: controller, ),
],
controller: controller,
),
), ),
), ),
)); ));
// 'Item 0' is covered by app bar.
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
// Item 0 is missing because its covered by the app bar.
new TestSemantics(
id: 2,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
label: 'Item 1',
),
new TestSemantics(
id: 3,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
label: 'Item 2',
),
new TestSemantics(
id: 4,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
label: 'Item 3',
),
],
),
new TestSemantics(
id: 5,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 6, tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar', children: <TestSemantics>[
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), new TestSemantics(
textDirection: TextDirection.ltr, flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'AppBar',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 4',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 5',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
], ],
) ),
], ],
), ),
ignoreTransform: true, ignoreTransform: true,
ignoreId: true,
ignoreRect: true,
)); ));
semantics.dispose(); semantics.dispose();
}); });
testWidgets('Slivers fully covered by another overlapping sliver are excluded', (WidgetTester tester) async { testWidgets('Slivers fully covered by another overlapping sliver are hidden', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
...@@ -429,79 +525,88 @@ void _tests() { ...@@ -429,79 +525,88 @@ void _tests() {
), ),
); );
}); });
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new MediaQuery(
controller: controller, data: const MediaQueryData(),
slivers: <Widget>[ child: new CustomScrollView(
const SliverAppBar( controller: controller,
pinned: true, slivers: <Widget>[
expandedHeight: 100.0, const SliverAppBar(
title: const Text('AppBar'), pinned: true,
), expandedHeight: 100.0,
]..addAll(slivers), title: const Text('AppBar'),
),
]..addAll(slivers),
),
), ),
), ),
)); ));
// 'Item 0' is covered by app bar.
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 2,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
label: 'Item 3',
),
new TestSemantics(
id: 3,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
label: 'Item 2',
),
new TestSemantics(
id: 4,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
label: 'Item 1',
),
// Item 0 is missing because its covered by the app bar.
],
),
new TestSemantics(
id: 5,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 6, tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), children: <TestSemantics>[
label: 'AppBar', new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'AppBar',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Item 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 4',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 5',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
], ],
) ),
], ],
), ),
ignoreTransform: true, ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
)); ));
semantics.dispose(); semantics.dispose();
...@@ -517,88 +622,98 @@ void _tests() { ...@@ -517,88 +622,98 @@ void _tests() {
); );
}); });
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new MediaQuery(
reverse: true, // This is the important setting for this test. data: const MediaQueryData(),
slivers: <Widget>[ child: new CustomScrollView(
const SliverAppBar( reverse: true, // This is the important setting for this test.
pinned: true, slivers: <Widget>[
expandedHeight: 100.0, const SliverAppBar(
title: const Text('AppBar'), pinned: true,
), expandedHeight: 100.0,
new SliverList( title: const Text('AppBar'),
delegate: new SliverChildListDelegate(listChildren), ),
), new SliverList(
], delegate: new SliverChildListDelegate(listChildren),
controller: controller, ),
],
controller: controller,
),
), ),
), ),
)); ));
// 'Item 0' is covered by app bar.
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
// Item 0 is missing because its covered by the app bar.
new TestSemantics( new TestSemantics(
id: 2, actions: <SemanticsAction>[
// Item 1 ends at 580dp, so there would be 20dp space for Item 0. SemanticsAction.scrollUp,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), SemanticsAction.scrollDown,
label: 'Item 1', ],
), children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 3, flags: <SemanticsFlag>[SemanticsFlag.isHidden],
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), label: 'Item 5',
label: 'Item 2', textDirection: TextDirection.ltr,
), ),
new TestSemantics( new TestSemantics(
id: 4, flags: <SemanticsFlag>[SemanticsFlag.isHidden],
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), label: 'Item 4',
label: 'Item 3', textDirection: TextDirection.ltr,
), ),
], new TestSemantics(
), label: 'Item 3',
new TestSemantics( textDirection: TextDirection.ltr,
id: 5, ),
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), new TestSemantics(
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)), label: 'Item 2',
flags: <SemanticsFlag>[SemanticsFlag.namesRoute], textDirection: TextDirection.ltr,
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling], ),
children: <TestSemantics>[ new TestSemantics(
new TestSemantics( label: 'Item 1',
id: 6, textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), ),
label: 'AppBar', new TestSemantics(
textDirection: TextDirection.ltr, flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 0',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'AppBar',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
], ],
) ),
], ],
), ),
ignoreTransform: true, ignoreTransform: true,
ignoreId: true,
ignoreRect: true,
)); ));
semantics.dispose(); semantics.dispose();
}); });
testWidgets('Slivers fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async { testWidgets('Slivers fully covered by another overlapping sliver are hidden (reverse)', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
...@@ -610,89 +725,93 @@ void _tests() { ...@@ -610,89 +725,93 @@ void _tests() {
), ),
); );
}); });
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new MediaQuery(
reverse: true, // This is the important setting for this test. data: const MediaQueryData(),
controller: controller, child: new CustomScrollView(
slivers: <Widget>[ reverse: true, // This is the important setting for this test.
const SliverAppBar( controller: controller,
pinned: true, slivers: <Widget>[
expandedHeight: 100.0, const SliverAppBar(
title: const Text('AppBar'), pinned: true,
), expandedHeight: 100.0,
]..addAll(slivers), title: const Text('AppBar'),
),
]..addAll(slivers),
),
), ),
), ),
)); ));
// 'Item 0' is covered by app bar.
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics(
id: 1, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 7,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, actions: <SemanticsAction>[SemanticsAction.scrollUp,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), SemanticsAction.scrollDown],
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)), children: <TestSemantics>[
label: 'Item 3', new TestSemantics(
), flags: <SemanticsFlag>[SemanticsFlag.isHidden],
new TestSemantics( label: 'Item 5',
id: 3, textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), ),
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)), new TestSemantics(
label: 'Item 2', flags: <SemanticsFlag>[SemanticsFlag.isHidden],
), label: 'Item 4',
new TestSemantics( textDirection: TextDirection.ltr,
id: 4, ),
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0), new TestSemantics(
// Item 1 ends at 580dp, so there would be 20dp space for Item 0. label: 'Item 3',
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)), textDirection: TextDirection.ltr,
label: 'Item 1', ),
), new TestSemantics(
// Item 0 is missing because its covered by the app bar. label: 'Item 2',
], textDirection: TextDirection.ltr,
), ),
new TestSemantics( new TestSemantics(
id: 5, label: 'Item 1',
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), textDirection: TextDirection.ltr,
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)), ),
flags: <SemanticsFlag>[SemanticsFlag.namesRoute], new TestSemantics(
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling], flags: <SemanticsFlag>[SemanticsFlag.isHidden],
children: <TestSemantics>[ label: 'Item 0',
new TestSemantics( textDirection: TextDirection.ltr,
id: 6, ),
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), ],
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)), ),
label: 'AppBar', new TestSemantics(
textDirection: TextDirection.ltr, tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'AppBar',
textDirection: TextDirection.ltr,
),
],
), ),
], ],
), ),
], ],
) ),
], ],
), ),
ignoreTransform: true, ignoreTransform: true,
ignoreId: true,
ignoreRect: true,
)); ));
semantics.dispose(); semantics.dispose();
}); });
testWidgets('Slivers fully covered by another overlapping sliver are excluded (with center sliver)', (WidgetTester tester) async { testWidgets('Slivers fully covered by another overlapping sliver are hidden (with center sliver)', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
...@@ -709,53 +828,177 @@ void _tests() { ...@@ -709,53 +828,177 @@ void _tests() {
child: new Text('Backward Item $i', textDirection: TextDirection.ltr), child: new Text('Backward Item $i', textDirection: TextDirection.ltr),
); );
}); });
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Semantics(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new MediaQuery( child: new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new Scrollable( child: new MediaQuery(
controller: controller, data: const MediaQueryData(),
viewportBuilder: (BuildContext context, ViewportOffset offset) { child: new Scrollable(
return new Viewport( controller: controller,
offset: offset, viewportBuilder: (BuildContext context, ViewportOffset offset) {
center: forwardAppBarKey, return new Viewport(
slivers: <Widget>[ offset: offset,
new SliverList( center: forwardAppBarKey,
delegate: new SliverChildListDelegate(backwardChildren), slivers: <Widget>[
), new SliverList(
const SliverAppBar( delegate: new SliverChildListDelegate(backwardChildren),
pinned: true, ),
expandedHeight: 100.0, const SliverAppBar(
flexibleSpace: const FlexibleSpaceBar( pinned: true,
title: const Text('Backward app bar', textDirection: TextDirection.ltr), expandedHeight: 100.0,
), flexibleSpace: const FlexibleSpaceBar(
), title: const Text('Backward app bar', textDirection: TextDirection.ltr),
new SliverAppBar( ),
pinned: true, ),
key: forwardAppBarKey, new SliverAppBar(
expandedHeight: 100.0, pinned: true,
flexibleSpace: const FlexibleSpaceBar( key: forwardAppBarKey,
title: const Text('Forward app bar', textDirection: TextDirection.ltr), expandedHeight: 100.0,
flexibleSpace: const FlexibleSpaceBar(
title: const Text('Forward app bar', textDirection: TextDirection.ltr),
),
),
new SliverList(
delegate: new SliverChildListDelegate(forwardChildren),
), ),
), ],
new SliverList( );
delegate: new SliverChildListDelegate(forwardChildren), },
), ),
],
);
},
), ),
), ),
)); ));
// 'Forward Item 0' is covered by app bar. // 'Forward Item 0' is covered by app bar.
expect(semantics, isNot(includesNodeWith(label: 'Forward Item 0'))); expect(semantics, hasSemantics(
expect(semantics, includesNodeWith(label: 'Forward Item 1')); new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'Forward app bar',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Forward Item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Forward Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Forward Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Forward Item 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Forward Item 4',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Forward Item 5',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
controller.jumpTo(-880.0); controller.jumpTo(-880.0);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(semantics, isNot(includesNodeWith(label: 'Backward Item 0')));
expect(semantics, includesNodeWith(label: 'Backward Item 1')); // 'Backward Item 0' is covered by app bar.
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Backward Item 5',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Backward Item 4',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Backward Item 3',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Backward Item 2',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: 'Backward Item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Backward Item 0',
textDirection: TextDirection.ltr,
),
],
),
new TestSemantics(
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
label: 'Backward app bar',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true,
));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -70,8 +70,8 @@ void main() { ...@@ -70,8 +70,8 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
verifyPaintPosition(key1, const Offset(0.0, 0.0), true); verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
verifyPaintPosition(key2, const Offset(0.0, 600.0), false); verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
verifyPaintPosition(key3, const Offset(0.0, 600.0), false); verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
position.animateTo(bigHeight - 600.0 + delegate.maxExtent, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(bigHeight - 600.0 + delegate.maxExtent, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 1000)); await tester.pumpAndSettle(const Duration(milliseconds: 1000));
...@@ -142,8 +142,8 @@ void main() { ...@@ -142,8 +142,8 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
verifyPaintPosition(key1, const Offset(0.0, 0.0), true); verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
verifyPaintPosition(key2, const Offset(0.0, 600.0), false); verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
verifyPaintPosition(key3, const Offset(0.0, 600.0), false); verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 1000)); await tester.pumpAndSettle(const Duration(milliseconds: 1000));
...@@ -177,8 +177,8 @@ void main() { ...@@ -177,8 +177,8 @@ void main() {
final ScrollPositionWithSingleContext position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPositionWithSingleContext position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
verifyPaintPosition(key1, const Offset(0.0, 0.0), true); verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
verifyPaintPosition(key2, const Offset(0.0, 600.0), false); verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
verifyPaintPosition(key3, const Offset(0.0, 600.0), false); verifyPaintPosition(key3, const Offset(0.0, 1200.0), false);
position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 1000)); await tester.pumpAndSettle(const Duration(milliseconds: 1000));
......
...@@ -90,9 +90,11 @@ void main() { ...@@ -90,9 +90,11 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n' ' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n' ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 200.0, paintExtent: 200.0,\n' ' │ geometry: SliverGeometry(scrollExtent: 200.0, paintExtent: 200.0,\n'
' │ maxPaintExtent: 200.0, hasVisualOverflow: true)\n' ' │ maxPaintExtent: 200.0, hasVisualOverflow: true, cacheExtent:\n'
' │ 200.0)\n'
' │ maxExtent: EXCEPTION (FlutterError)\n' ' │ maxExtent: EXCEPTION (FlutterError)\n'
' │ child position: 0.0\n' ' │ child position: 0.0\n'
' │\n' ' │\n'
...@@ -140,23 +142,23 @@ void main() { ...@@ -140,23 +142,23 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
verifyPaintPosition(key1, const Offset(0.0, 0.0), true); verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
verifyPaintPosition(key2, const Offset(0.0, 550.0), true); verifyPaintPosition(key2, const Offset(0.0, 550.0), true);
verifyPaintPosition(key3, const Offset(0.0, 600.0), false); verifyPaintPosition(key3, const Offset(0.0, 750.0), false);
verifyPaintPosition(key4, const Offset(0.0, 600.0), false); verifyPaintPosition(key4, const Offset(0.0, 950.0), false);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 1500.0), false);
position.animateTo(550.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(550.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 100)); await tester.pumpAndSettle(const Duration(milliseconds: 100));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
verifyPaintPosition(key2, const Offset(0.0, 0.0), true); verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
verifyPaintPosition(key3, const Offset(0.0, 200.0), true); verifyPaintPosition(key3, const Offset(0.0, 200.0), true);
verifyPaintPosition(key4, const Offset(0.0, 400.0), true); verifyPaintPosition(key4, const Offset(0.0, 400.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 950.0), false);
position.animateTo(600.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(600.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 200)); await tester.pumpAndSettle(const Duration(milliseconds: 200));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
verifyPaintPosition(key2, const Offset(0.0, 0.0), true); verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
verifyPaintPosition(key3, const Offset(0.0, 150.0), true); verifyPaintPosition(key3, const Offset(0.0, 150.0), true);
verifyPaintPosition(key4, const Offset(0.0, 350.0), true); verifyPaintPosition(key4, const Offset(0.0, 350.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 900.0), false);
position.animateTo(650.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(650.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 300)); await tester.pumpAndSettle(const Duration(milliseconds: 300));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
...@@ -164,7 +166,7 @@ void main() { ...@@ -164,7 +166,7 @@ void main() {
verifyPaintPosition(key3, const Offset(0.0, 100.0), true); verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0)); verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
verifyPaintPosition(key4, const Offset(0.0, 300.0), true); verifyPaintPosition(key4, const Offset(0.0, 300.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 850.0), false);
position.animateTo(700.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(700.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 400)); await tester.pumpAndSettle(const Duration(milliseconds: 400));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
...@@ -172,7 +174,7 @@ void main() { ...@@ -172,7 +174,7 @@ void main() {
verifyPaintPosition(key3, const Offset(0.0, 100.0), true); verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0)); verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
verifyPaintPosition(key4, const Offset(0.0, 250.0), true); verifyPaintPosition(key4, const Offset(0.0, 250.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 800.0), false);
position.animateTo(750.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(750.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 500)); await tester.pumpAndSettle(const Duration(milliseconds: 500));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
...@@ -180,21 +182,21 @@ void main() { ...@@ -180,21 +182,21 @@ void main() {
verifyPaintPosition(key3, const Offset(0.0, 100.0), true); verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0)); verifyActualBoxPosition(tester, find.byType(Container), 1, new Rect.fromLTWH(0.0, 100.0, 800.0, 200.0));
verifyPaintPosition(key4, const Offset(0.0, 200.0), true); verifyPaintPosition(key4, const Offset(0.0, 200.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 750.0), false);
position.animateTo(800.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(800.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 60)); await tester.pumpAndSettle(const Duration(milliseconds: 60));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
verifyPaintPosition(key2, const Offset(0.0, 0.0), true); verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
verifyPaintPosition(key3, const Offset(0.0, 100.0), true); verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
verifyPaintPosition(key4, const Offset(0.0, 150.0), true); verifyPaintPosition(key4, const Offset(0.0, 150.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 700.0), false);
position.animateTo(850.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(850.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 70)); await tester.pumpAndSettle(const Duration(milliseconds: 70));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
verifyPaintPosition(key2, const Offset(0.0, 0.0), true); verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
verifyPaintPosition(key3, const Offset(0.0, 100.0), true); verifyPaintPosition(key3, const Offset(0.0, 100.0), true);
verifyPaintPosition(key4, const Offset(0.0, 100.0), true); verifyPaintPosition(key4, const Offset(0.0, 100.0), true);
verifyPaintPosition(key5, const Offset(0.0, 600.0), false); verifyPaintPosition(key5, const Offset(0.0, 650.0), false);
position.animateTo(900.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animateTo(900.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 80)); await tester.pumpAndSettle(const Duration(milliseconds: 80));
verifyPaintPosition(key1, const Offset(0.0, 0.0), false); verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
......
...@@ -22,12 +22,15 @@ class _GenerationTextState extends State<GenerationText> { ...@@ -22,12 +22,15 @@ class _GenerationTextState extends State<GenerationText> {
Widget build(BuildContext context) => new Text('${widget.value}:$generation ', textDirection: TextDirection.ltr); Widget build(BuildContext context) => new Text('${widget.value}:$generation ', textDirection: TextDirection.ltr);
} }
// Creates a SliverList with `keys.length` children and each child having a key from `keys` and a text of `key:generation`.
// The generation is increased with every call to this method.
Future<Null> test(WidgetTester tester, double offset, List<int> keys) { Future<Null> test(WidgetTester tester, double offset, List<int> keys) {
globalGeneration += 1; globalGeneration += 1;
return tester.pumpWidget( return tester.pumpWidget(
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Viewport( child: new Viewport(
cacheExtent: 0.0,
offset: new ViewportOffset.fixed(offset), offset: new ViewportOffset.fixed(offset),
slivers: <Widget>[ slivers: <Widget>[
new SliverList( new SliverList(
...@@ -41,6 +44,8 @@ Future<Null> test(WidgetTester tester, double offset, List<int> keys) { ...@@ -41,6 +44,8 @@ Future<Null> test(WidgetTester tester, double offset, List<int> keys) {
); );
} }
// `answerKey`: Expected offsets of visible SliverList children in global coordinate system.
// `text`: A space-separated list of expected `key:generation` pairs for the visible SliverList children.
void verify(WidgetTester tester, List<Offset> answerKey, String text) { void verify(WidgetTester tester, List<Offset> answerKey, String text) {
final List<Offset> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Offset>( final List<Offset> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Offset>(
(RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0)) (RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
...@@ -48,8 +53,8 @@ void verify(WidgetTester tester, List<Offset> answerKey, String text) { ...@@ -48,8 +53,8 @@ void verify(WidgetTester tester, List<Offset> answerKey, String text) {
expect(testAnswers, equals(answerKey)); expect(testAnswers, equals(answerKey));
final String foundText = final String foundText =
tester.widgetList<Text>(find.byType(Text)) tester.widgetList<Text>(find.byType(Text))
.map<String>((Text widget) => widget.data) .map<String>((Text widget) => widget.data)
.reduce((String value, String element) => value + element); .reduce((String value, String element) => value + element);
expect(foundText, equals(text)); expect(foundText, equals(text));
} }
......
...@@ -198,6 +198,7 @@ void main() { ...@@ -198,6 +198,7 @@ void main() {
testWidgets('Removing offscreen items above and rescrolling does not crash', (WidgetTester tester) async { testWidgets('Removing offscreen items above and rescrolling does not crash', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp( await tester.pumpWidget(new MaterialApp(
home: new CustomScrollView( home: new CustomScrollView(
cacheExtent: 0.0,
slivers: <Widget>[ slivers: <Widget>[
new SliverFixedExtentList( new SliverFixedExtentList(
itemExtent: 100.0, itemExtent: 100.0,
...@@ -225,6 +226,7 @@ void main() { ...@@ -225,6 +226,7 @@ void main() {
// Stop returning the first 3 items. // Stop returning the first 3 items.
await tester.pumpWidget(new MaterialApp( await tester.pumpWidget(new MaterialApp(
home: new CustomScrollView( home: new CustomScrollView(
cacheExtent: 0.0,
slivers: <Widget>[ slivers: <Widget>[
new SliverFixedExtentList( new SliverFixedExtentList(
itemExtent: 100.0, itemExtent: 100.0,
......
...@@ -27,7 +27,7 @@ Future<Null> test(WidgetTester tester, double offset, EdgeInsetsGeometry padding ...@@ -27,7 +27,7 @@ Future<Null> test(WidgetTester tester, double offset, EdgeInsetsGeometry padding
} }
void verify(WidgetTester tester, List<Rect> answerKey) { void verify(WidgetTester tester, List<Rect> answerKey) {
final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>( final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Rect>(
(RenderBox target) { (RenderBox target) {
final Offset topLeft = target.localToGlobal(Offset.zero); final Offset topLeft = target.localToGlobal(Offset.zero);
final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero)); final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero));
...@@ -45,14 +45,14 @@ void main() { ...@@ -45,14 +45,14 @@ void main() {
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0), new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
]); ]);
await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr); await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr);
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0), new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0), new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
]); ]);
await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr); await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr);
...@@ -84,14 +84,14 @@ void main() { ...@@ -84,14 +84,14 @@ void main() {
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0), new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
]); ]);
await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr); await test(tester, 200.0, padding, AxisDirection.down, TextDirection.ltr);
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0), new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0), new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
]); ]);
await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr); await test(tester, 390.0, padding, AxisDirection.down, TextDirection.ltr);
...@@ -123,14 +123,14 @@ void main() { ...@@ -123,14 +123,14 @@ void main() {
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
new Rect.fromLTWH(15.0, 420.0, 760.0, 400.0), new Rect.fromLTWH(15.0, 420.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 855.0, 800.0, 400.0),
]); ]);
await test(tester, 200.0, padding, AxisDirection.down, TextDirection.rtl); await test(tester, 200.0, padding, AxisDirection.down, TextDirection.rtl);
verify(tester, <Rect>[ verify(tester, <Rect>[
new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0), new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
new Rect.fromLTWH(15.0, 220.0, 760.0, 400.0), new Rect.fromLTWH(15.0, 220.0, 760.0, 400.0),
new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0), new Rect.fromLTWH(0.0, 655.0, 800.0, 400.0),
]); ]);
await test(tester, 390.0, padding, AxisDirection.down, TextDirection.rtl); await test(tester, 390.0, padding, AxisDirection.down, TextDirection.rtl);
...@@ -363,6 +363,7 @@ void main() { ...@@ -363,6 +363,7 @@ void main() {
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new CustomScrollView( child: new CustomScrollView(
cacheExtent: 0.0,
slivers: <Widget>[ slivers: <Widget>[
new SliverPadding( new SliverPadding(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
......
...@@ -26,10 +26,10 @@ Future<Null> test(WidgetTester tester, double offset, { double anchor: 0.0 }) { ...@@ -26,10 +26,10 @@ Future<Null> test(WidgetTester tester, double offset, { double anchor: 0.0 }) {
} }
void verify(WidgetTester tester, List<Offset> idealPositions, List<bool> idealVisibles) { void verify(WidgetTester tester, List<Offset> idealPositions, List<bool> idealVisibles) {
final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Offset>( final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Offset>(
(RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0)) (RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
).toList(); ).toList();
final List<bool> actualVisibles = tester.renderObjectList<RenderSliverToBoxAdapter>(find.byType(SliverToBoxAdapter)).map<bool>( final List<bool> actualVisibles = tester.renderObjectList<RenderSliverToBoxAdapter>(find.byType(SliverToBoxAdapter, skipOffstage: false)).map<bool>(
(RenderSliverToBoxAdapter target) => target.geometry.visible (RenderSliverToBoxAdapter target) => target.geometry.visible
).toList(); ).toList();
expect(actualPositions, equals(idealPositions)); expect(actualPositions, equals(idealPositions));
...@@ -43,9 +43,9 @@ void main() { ...@@ -43,9 +43,9 @@ void main() {
verify(tester, <Offset>[ verify(tester, <Offset>[
const Offset(0.0, 0.0), const Offset(0.0, 0.0),
const Offset(0.0, 400.0), const Offset(0.0, 400.0),
const Offset(0.0, 600.0), const Offset(0.0, 800.0),
const Offset(0.0, 600.0), const Offset(0.0, 1200.0),
const Offset(0.0, 600.0), const Offset(0.0, 1600.0),
], <bool>[true, true, false, false, false]); ], <bool>[true, true, false, false, false]);
await test(tester, 200.0); await test(tester, 200.0);
...@@ -53,8 +53,8 @@ void main() { ...@@ -53,8 +53,8 @@ void main() {
const Offset(0.0, -200.0), const Offset(0.0, -200.0),
const Offset(0.0, 200.0), const Offset(0.0, 200.0),
const Offset(0.0, 600.0), const Offset(0.0, 600.0),
const Offset(0.0, 600.0), const Offset(0.0, 1000.0),
const Offset(0.0, 600.0), const Offset(0.0, 1400.0),
], <bool>[true, true, false, false, false]); ], <bool>[true, true, false, false, false]);
await test(tester, 600.0); await test(tester, 600.0);
...@@ -63,7 +63,7 @@ void main() { ...@@ -63,7 +63,7 @@ void main() {
const Offset(0.0, -200.0), const Offset(0.0, -200.0),
const Offset(0.0, 200.0), const Offset(0.0, 200.0),
const Offset(0.0, 600.0), const Offset(0.0, 600.0),
const Offset(0.0, 600.0), const Offset(0.0, 1000.0),
], <bool>[false, true, true, false, false]); ], <bool>[false, true, true, false, false]);
await test(tester, 900.0); await test(tester, 900.0);
...@@ -72,7 +72,7 @@ void main() { ...@@ -72,7 +72,7 @@ void main() {
const Offset(0.0, -500.0), const Offset(0.0, -500.0),
const Offset(0.0, -100.0), const Offset(0.0, -100.0),
const Offset(0.0, 300.0), const Offset(0.0, 300.0),
const Offset(0.0, 600.0), const Offset(0.0, 700.0),
], <bool>[false, false, true, true, false]); ], <bool>[false, false, true, true, false]);
}); });
...@@ -82,18 +82,18 @@ void main() { ...@@ -82,18 +82,18 @@ void main() {
verify(tester, <Offset>[ verify(tester, <Offset>[
const Offset(0.0, 100.0), const Offset(0.0, 100.0),
const Offset(0.0, 500.0), const Offset(0.0, 500.0),
const Offset(0.0, 600.0), const Offset(0.0, 900.0),
const Offset(0.0, 600.0), const Offset(0.0, 1300.0),
const Offset(0.0, 600.0), const Offset(0.0, 1700.0),
], <bool>[true, true, false, false, false]); ], <bool>[true, true, false, false, false]);
await test(tester, 200.0, anchor: 100.0); await test(tester, 200.0, anchor: 100.0);
verify(tester, <Offset>[ verify(tester, <Offset>[
const Offset(0.0, -100.0), const Offset(0.0, -100.0),
const Offset(0.0, 300.0), const Offset(0.0, 300.0),
const Offset(0.0, 600.0), const Offset(0.0, 700.0),
const Offset(0.0, 600.0), const Offset(0.0, 1100.0),
const Offset(0.0, 600.0), const Offset(0.0, 1500.0),
], <bool>[true, true, false, false, false]); ], <bool>[true, true, false, false, false]);
await test(tester, 600.0, anchor: 100.0); await test(tester, 600.0, anchor: 100.0);
...@@ -101,8 +101,8 @@ void main() { ...@@ -101,8 +101,8 @@ void main() {
const Offset(0.0, -500.0), const Offset(0.0, -500.0),
const Offset(0.0, -100.0), const Offset(0.0, -100.0),
const Offset(0.0, 300.0), const Offset(0.0, 300.0),
const Offset(0.0, 600.0), const Offset(0.0, 700.0),
const Offset(0.0, 600.0), const Offset(0.0, 1100.0),
], <bool>[false, true, true, false, false]); ], <bool>[false, true, true, false, false]);
await test(tester, 900.0, anchor: 100.0); await test(tester, 900.0, anchor: 100.0);
...@@ -111,7 +111,7 @@ void main() { ...@@ -111,7 +111,7 @@ void main() {
const Offset(0.0, -400.0), const Offset(0.0, -400.0),
const Offset(0.0, 0.0), const Offset(0.0, 0.0),
const Offset(0.0, 400.0), const Offset(0.0, 400.0),
const Offset(0.0, 600.0), const Offset(0.0, 800.0),
], <bool>[false, false, true, true, false]); ], <bool>[false, false, true, true, false]);
}); });
......
...@@ -21,9 +21,9 @@ void main() { ...@@ -21,9 +21,9 @@ void main() {
), ),
)); ));
final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer'))); final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer'), skipOffstage: false));
expect(outerTracker.captionFontSize, 12.0); expect(outerTracker.captionFontSize, 12.0);
final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner'))); final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner'), skipOffstage: false));
expect(innerTracker.captionFontSize, 13.0); expect(innerTracker.captionFontSize, 13.0);
}); });
......
...@@ -55,8 +55,8 @@ class CommonFinders { ...@@ -55,8 +55,8 @@ class CommonFinders {
/// nodes that are [Offstage] or that are from inactive [Route]s. /// nodes that are [Offstage] or that are from inactive [Route]s.
Finder widgetWithText(Type widgetType, String text, { bool skipOffstage: true }) { Finder widgetWithText(Type widgetType, String text, { bool skipOffstage: true }) {
return find.ancestor( return find.ancestor(
of: find.text(text), of: find.text(text, skipOffstage: skipOffstage),
matching: find.byType(widgetType), matching: find.byType(widgetType, skipOffstage: skipOffstage),
); );
} }
......
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