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
......@@ -140,11 +140,11 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
final double itemExtent = this.itemExtent;
final double scrollOffset = constraints.scrollOffset;
final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent;
assert(remainingPaintExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints(
minExtent: itemExtent,
......@@ -242,9 +242,16 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
to: trailingScrollOffset,
);
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: (targetLastIndex != null && lastIndex >= targetLastIndex)
......
......@@ -513,11 +513,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout();
childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset;
final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent;
assert(remainingPaintExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingExtent;
final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
......@@ -617,11 +617,17 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
from: leadingScrollOffset,
to: trailingScrollOffset,
);
final double cacheExtent = calculateCacheOffset(
constraints,
from: leadingScrollOffset,
to: trailingScrollOffset,
);
geometry = new SliverGeometry(
scrollExtent: estimatedTotalExtent,
paintExtent: paintExtent,
maxPaintExtent: estimatedTotalExtent,
cacheExtent: cacheExtent,
// Conservative to avoid complexity.
hasVisualOverflow: true,
);
......
......@@ -47,11 +47,11 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
childManager.didStartLayout();
childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset;
final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
assert(scrollOffset >= 0.0);
final double remainingPaintExtent = constraints.remainingPaintExtent;
assert(remainingPaintExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingPaintExtent;
final double remainingExtent = constraints.remainingCacheExtent;
assert(remainingExtent >= 0.0);
final double targetEndScrollOffset = scrollOffset + remainingExtent;
final BoxConstraints childConstraints = constraints.asBoxConstraints();
int leadingGarbage = 0;
int trailingGarbage = 0;
......@@ -269,9 +269,15 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
from: childScrollOffset(firstChild),
to: endScrollOffset,
);
final double cacheExtent = calculateCacheOffset(
constraints,
from: childScrollOffset(firstChild),
to: endScrollOffset,
);
geometry = new SliverGeometry(
scrollExtent: estimatedMaxScrollOffset,
paintExtent: paintExtent,
cacheExtent: cacheExtent,
maxPaintExtent: estimatedMaxScrollOffset,
// Conservative to avoid flickering away the clip during scroll.
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
......
......@@ -298,36 +298,6 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
_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
/// scroll offset.
///
......
......@@ -182,8 +182,10 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
child.layout(
constraints.copyWith(
scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
overlap: 0.0,
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),
),
parentUsesSize: true,
......@@ -206,6 +208,17 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
to: mainAxisPadding + childLayoutGeometry.scrollExtent,
);
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(
beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
constraints.remainingPaintExtent,
......@@ -214,6 +227,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
paintExtent: paintExtent,
layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
hitTestExtent: math.max(
mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
......
......@@ -292,13 +292,15 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent
final bool overlapsContent = constraints.overlap > 0.0;
excludeFromSemanticsScrolling = overlapsContent || (constraints.scrollOffset > maxExtent - minExtent);
layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
geometry = new SliverGeometry(
scrollExtent: maxExtent,
paintOrigin: constraints.overlap,
paintExtent: math.min(childExtent, constraints.remainingPaintExtent),
layoutExtent: (maxExtent - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent),
layoutExtent: layoutExtent,
maxPaintExtent: maxExtent,
maxScrollObstructionExtent: minExtent,
cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
);
}
......
......@@ -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
/// 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.
///
......
......@@ -564,6 +564,7 @@ class _PageViewState extends State<PageView> {
physics: physics,
viewportBuilder: (BuildContext context, ViewportOffset position) {
return new Viewport(
cacheExtent: 0.0,
axisDirection: axisDirection,
offset: position,
slivers: <Widget>[
......
......@@ -56,6 +56,7 @@ abstract class ScrollView extends StatelessWidget {
bool primary,
ScrollPhysics physics,
this.shrinkWrap: false,
this.cacheExtent,
}) : assert(reverse != null),
assert(shrinkWrap != null),
assert(!(controller != null && primary == true),
......@@ -165,6 +166,9 @@ abstract class ScrollView extends StatelessWidget {
/// Defaults to false.
final bool shrinkWrap;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Returns the [AxisDirection] in which the scroll view scrolls.
///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the
......@@ -211,6 +215,7 @@ abstract class ScrollView extends StatelessWidget {
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
cacheExtent: cacheExtent,
);
}
......@@ -333,6 +338,7 @@ class CustomScrollView extends ScrollView {
bool primary,
ScrollPhysics physics,
bool shrinkWrap: false,
double cacheExtent,
this.slivers: const <Widget>[],
}) : super(
key: key,
......@@ -342,6 +348,7 @@ class CustomScrollView extends ScrollView {
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
);
/// The slivers to place inside the viewport.
......@@ -372,6 +379,7 @@ abstract class BoxScrollView extends ScrollView {
ScrollPhysics physics,
bool shrinkWrap: false,
this.padding,
double cacheExtent,
}) : super(
key: key,
scrollDirection: scrollDirection,
......@@ -380,6 +388,7 @@ abstract class BoxScrollView extends ScrollView {
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
cacheExtent: cacheExtent,
);
/// The amount of space by which to inset the children.
......@@ -594,6 +603,7 @@ class ListView extends BoxScrollView {
this.itemExtent,
bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate(
children,
......@@ -608,6 +618,7 @@ class ListView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// Creates a scrollable, linear array of widgets that are created on demand.
......@@ -648,6 +659,7 @@ class ListView extends BoxScrollView {
int itemCount,
bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true,
double cacheExtent,
}) : childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
......@@ -662,6 +674,7 @@ class ListView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent
);
/// Creates a scrollable, linear array of widgets with a custom child model.
......@@ -679,6 +692,7 @@ class ListView extends BoxScrollView {
EdgeInsetsGeometry padding,
this.itemExtent,
@required this.childrenDelegate,
double cacheExtent,
}) : assert(childrenDelegate != null),
super(
key: key,
......@@ -689,6 +703,7 @@ class ListView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// If non-null, forces the children to have the given extent in the scroll
......@@ -878,6 +893,7 @@ class GridView extends BoxScrollView {
@required this.gridDelegate,
bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[],
}) : assert(gridDelegate != null),
childrenDelegate = new SliverChildListDelegate(
......@@ -894,6 +910,7 @@ class GridView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// Creates a scrollable, 2D array of widgets that are created on demand.
......@@ -929,6 +946,7 @@ class GridView extends BoxScrollView {
int itemCount,
bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true,
double cacheExtent,
}) : assert(gridDelegate != null),
childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder,
......@@ -945,6 +963,7 @@ class GridView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// Creates a scrollable, 2D array of widgets with both a custom
......@@ -965,6 +984,7 @@ class GridView extends BoxScrollView {
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required this.childrenDelegate,
double cacheExtent,
}) : assert(gridDelegate != null),
assert(childrenDelegate != null),
super(
......@@ -976,6 +996,7 @@ class GridView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
......@@ -1007,6 +1028,7 @@ class GridView extends BoxScrollView {
double childAspectRatio: 1.0,
bool addAutomaticKeepAlives: true,
bool addRepaintBoundaries: true,
double cacheExtent,
List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
......@@ -1027,6 +1049,7 @@ class GridView extends BoxScrollView {
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
);
/// Creates a scrollable, 2D array of widgets with tiles that each have a
......
......@@ -323,11 +323,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
_RenderSingleChildViewport({
AxisDirection axisDirection: AxisDirection.down,
@required ViewportOffset offset,
double cacheExtent: RenderAbstractViewport.defaultCacheExtent,
RenderBox child,
}) : assert(axisDirection != null),
assert(offset != null),
assert(cacheExtent != null),
_axisDirection = axisDirection,
_offset = offset {
_offset = offset,
_cacheExtent = cacheExtent {
this.child = child;
}
......@@ -357,6 +360,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
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() {
markNeedsPaint();
markNeedsSemanticsUpdate();
......@@ -588,4 +602,26 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
// Make sure the viewport itself is on screen.
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
assert(!_childElements.values.any((Element child) => child == null));
_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
......
......@@ -56,6 +56,7 @@ class Viewport extends MultiChildRenderObjectWidget {
this.anchor: 0.0,
@required this.offset,
this.center,
this.cacheExtent,
List<Widget> slivers: const <Widget>[],
}) : assert(offset != null),
assert(slivers != null),
......@@ -108,6 +109,9 @@ class Viewport extends MultiChildRenderObjectWidget {
/// The [center] must be the key of a child of the viewport.
final Key center;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross
/// axis direction.
///
......@@ -135,6 +139,7 @@ class Viewport extends MultiChildRenderObjectWidget {
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor,
offset: offset,
cacheExtent: cacheExtent,
);
}
......@@ -144,7 +149,8 @@ class Viewport extends MultiChildRenderObjectWidget {
..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
..anchor = anchor
..offset = offset;
..offset = offset
..cacheExtent = cacheExtent;
}
@override
......@@ -199,6 +205,14 @@ class _ViewportElement extends MultiChildRenderObjectElement {
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
......
......@@ -430,11 +430,11 @@ void main() {
// Now the sliver is scrolled off screen.
expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy,
tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536),
);
expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy,
tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536),
);
expect(
......@@ -720,11 +720,11 @@ void main() {
// Now the sliver is scrolled off screen.
expect(
tester.getTopLeft(find.widgetWithText(Center, '-1')).dy,
tester.getTopLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-175.38461538461536),
);
expect(
tester.getBottomLeft(find.widgetWithText(Center, '-1')).dy,
tester.getBottomLeft(find.widgetWithText(Center, '-1', skipOffstage: false)).dy,
moreOrLessEquals(-115.38461538461536),
);
......@@ -872,7 +872,7 @@ void main() {
);
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive,
);
......@@ -907,7 +907,7 @@ void main() {
await tester.pump(const Duration(seconds: 2));
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive,
);
......@@ -1147,7 +1147,7 @@ void main() {
moreOrLessEquals(-145.0332383665717),
);
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh,
);
......@@ -1155,7 +1155,7 @@ void main() {
// The sliver layout extent is removed on next frame.
await tester.pump();
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive,
);
// Nothing moved.
......@@ -1208,7 +1208,7 @@ void main() {
await tester.pump(const Duration(seconds: 5));
// In refresh mode but has no UI.
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.refresh,
);
expect(
......@@ -1221,7 +1221,7 @@ void main() {
await tester.pump();
// Goes to inactive right away since the sliver is already collapsed.
expect(
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder))),
CupertinoRefreshControl.state(tester.element(find.byType(LayoutBuilder, skipOffstage: false))),
RefreshIndicatorMode.inactive,
);
......
......@@ -54,16 +54,11 @@ ScrollController primaryScrollController(WidgetTester tester) {
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
}
bool appBarIsVisible(WidgetTester tester) {
final RenderSliver sliver = tester.element(find.byType(SliverAppBar)).findRenderObject();
return sliver.geometry.visible;
}
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 appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar, skipOffstage: false)).dy;
double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar, skipOffstage: false)).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() {
setUp(() {
......@@ -592,7 +587,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
final double initialAppBarHeight = appBarHeight(tester);
final double initialTabBarHeight = tabBarHeight(tester);
......@@ -600,21 +595,21 @@ void main() {
// Scroll the not-pinned appbar partially out of view
controller.jumpTo(50.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar out of view
controller.jumpTo(600.0);
await tester.pump();
expect(appBarIsVisible(tester), false);
expect(find.byType(SliverAppBar), findsNothing);
expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
// Scroll the not-pinned appbar back into view
controller.jumpTo(0.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
});
......@@ -629,7 +624,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0;
......@@ -639,7 +634,7 @@ void main() {
// point both the toolbar and the tabbar are visible.
controller.jumpTo(600.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), greaterThan(initialTabBarHeight));
......@@ -647,7 +642,7 @@ void main() {
// Scroll the not-pinned appbar back into view
controller.jumpTo(0.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
});
......@@ -662,7 +657,7 @@ void main() {
final ScrollController controller = primaryScrollController(tester);
expect(controller.offset, 0.0);
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), 128.0);
const double initialAppBarHeight = 128.0;
......@@ -672,7 +667,7 @@ void main() {
// point only the tabBar is visible.
controller.jumpTo(600.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(tabBarHeight(tester), initialTabBarHeight);
expect(appBarHeight(tester), lessThan(initialAppBarHeight));
expect(appBarHeight(tester), initialTabBarHeight);
......@@ -680,7 +675,7 @@ void main() {
// Scroll the floating-pinned appbar back into view
controller.jumpTo(0.0);
await tester.pump();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), initialAppBarHeight);
expect(tabBarHeight(tester), initialTabBarHeight);
});
......@@ -692,7 +687,7 @@ void main() {
snap: true,
expandedHeight: 128.0,
));
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0);
......@@ -701,7 +696,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.00);
await tester.pumpAndSettle();
expect(appBarIsVisible(tester), false);
expect(find.byType(SliverAppBar), findsNothing);
expect(appBarTop(tester), lessThanOrEqualTo(-128.0));
// Drag the scrollable up and down. The app bar should not snap open, its
......@@ -773,7 +768,7 @@ void main() {
snap: true,
expandedHeight: 128.0,
));
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), 128.0);
expect(appBarBottom(tester), 128.0);
......@@ -783,7 +778,7 @@ void main() {
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(256.0);
await tester.pumpAndSettle();
expect(appBarIsVisible(tester), true);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarTop(tester), 0.0);
expect(appBarHeight(tester), kTextTabBarHeight);
......
......@@ -289,7 +289,7 @@ void main() {
}
StateMarkerState findStateMarkerState(String name) {
return tester.state(find.widgetWithText(StateMarker, name));
return tester.state(find.widgetWithText(StateMarker, name, skipOffstage: false));
}
await tester.pumpWidget(builder());
......
......@@ -162,7 +162,7 @@ void main() {
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump();
expect(find.byType(TextField), findsOneWidget);
expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
expect(tester.testTextInput.isVisible, isTrue);
focusNode.unfocus();
......@@ -201,10 +201,10 @@ void main() {
expect(find.byType(TextField), findsOneWidget);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump();
expect(find.byType(TextField), findsOneWidget);
expect(find.byType(TextField, skipOffstage: false), findsOneWidget);
await tester.pumpWidget(makeTest('test'));
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 {
......
This diff is collapsed.
......@@ -83,6 +83,7 @@ void main() {
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: new ViewportOffset.zero(),
cacheExtent: 0.0,
children: <RenderSliver>[
inner = childManager.createRenderObject(),
],
......@@ -161,6 +162,7 @@ void main() {
children: <RenderSliver>[
inner = childManager.createRenderObject(),
],
cacheExtent: 0.0,
);
layout(root);
......
......@@ -27,6 +27,8 @@ void main() {
crossAxisExtent: 0.0,
crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 0.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
);
final SliverConstraints b = a.copyWith();
expect(a, equals(b));
......@@ -55,6 +57,8 @@ void main() {
crossAxisExtent: 40.0,
crossAxisDirection: AxisDirection.right,
viewportMainAxisExtent: 30.0,
cacheOrigin: 0.0,
remainingCacheExtent: 0.0,
);
expect(c, equals(d));
expect(c.normalizedGrowthDirection, equals(GrowthDirection.forward));
......
......@@ -60,9 +60,9 @@ void main() {
),
);
double itemHeight(int index) => tester.getSize(find.byKey(new ValueKey<int>(index))).height;
double itemTop(int index) => tester.getTopLeft(find.byKey(new ValueKey<int>(index))).dy;
double itemBottom(int index) => tester.getBottomLeft(find.byKey(new ValueKey<int>(index))).dy;
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), skipOffstage: false)).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));
await tester.pump();
......
......@@ -168,9 +168,17 @@ void main() {
0, 1, 2, // col 0
3, 4, 5, // col 1
6, 7, 8, // col 2
9, 10, 11, // col 3 (in cached area)
]));
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 ScrollPosition position = state.position;
position.jumpTo(3025.0);
......@@ -179,25 +187,49 @@ void main() {
await tester.pump();
expect(log, equals(<int>[
30, 31, 32, // col 10 (in cached area)
33, 34, 35, // col 11
36, 37, 38, // col 12
39, 40, 41, // col 13
42, 43, 44, // col 14
45, 46, 47, // col 15 (in cached area)
]));
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);
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[
6, 7, 8, // col2 (in cached area)
9, 10, 11, // col 3
12, 13, 14, // col 4
15, 16, 17, // col 5
18, 19, 20, // col 6
21, 22, 23, // col 7 (in cached area)
]));
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 {
......@@ -230,7 +262,15 @@ void main() {
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
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();
await tester.pumpWidget(
......@@ -258,6 +298,8 @@ void main() {
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
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();
......@@ -295,7 +337,15 @@ void main() {
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
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();
await tester.pumpWidget(
......@@ -323,6 +373,8 @@ void main() {
0, 1, 2, 3, // row 0
4, 5, 6, 7, // row 1
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();
......@@ -346,6 +398,7 @@ void main() {
child: new SizedBox(
height: 200.0,
child: new GridView.count(
cacheExtent: 0.0,
crossAxisCount: 2,
children: <Widget>[ container, container, container, container ],
),
......@@ -379,7 +432,7 @@ void main() {
),
);
expect(find.text('0'), findsOneWidget);
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsNothing);
});
......
......@@ -781,6 +781,7 @@ void main() {
builder: (BuildContext context) {
return new Material(
child: new ListView(
cacheExtent: 0.0,
children: <Widget>[
const SizedBox(height: 100.0),
// This container will appear at Y=100
......
......@@ -38,7 +38,11 @@ void main() {
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();
testWidget.flip();
......@@ -50,7 +54,11 @@ void main() {
testWidget.flip();
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 {
......@@ -91,7 +99,12 @@ void main() {
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();
jumpTo(400.0);
......@@ -99,12 +112,12 @@ void main() {
await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4]));
callbackTracker.clear();
await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[2, 3, 4]));
expect(callbackTracker, equals(<int>[
0, 1, // in caching area
2, 3, 4,
5, 6, // in caching area
]));
check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5, 6]);
callbackTracker.clear();
jumpTo(500.0);
......@@ -112,7 +125,12 @@ void main() {
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();
});
......@@ -155,8 +173,12 @@ void main() {
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();
jumpTo(400.0);
......@@ -164,12 +186,12 @@ void main() {
await tester.pumpWidget(buildWidget());
expect(callbackTracker, equals(<int>[1, 2, 3, 4, 5]));
callbackTracker.clear();
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, 7, // in caching area
]));
check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6, 7]);
callbackTracker.clear();
jumpTo(500.0);
......@@ -177,7 +199,12 @@ void main() {
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();
});
......@@ -208,26 +235,39 @@ void main() {
}
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();
jumpTo(150.0);
await tester.pump();
expect(callbackTracker, equals(<int>[2]));
expect(callbackTracker, equals(<int>[3]));
check(visible: <int>[0, 1, 2], hidden: <int>[3]);
callbackTracker.clear();
jumpTo(600.0);
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();
jumpTo(750.0);
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();
});
}
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() {
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
cacheExtent: 0.0,
controller: controller,
children: <Widget>[
new Container(height: 400.0, child: const Text('1')),
......@@ -34,6 +35,7 @@ void main() {
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
cacheExtent: 0.0,
controller: controller,
children: <Widget>[
new Container(height: 200.0, child: const Text('1')),
......@@ -53,16 +55,73 @@ void main() {
controller.jumpTo(300.0);
await tester.pump();
expect(controller.offset, equals(300.0));
expect(tester.getTopLeft(find.text('2')).dy, equals(100.0));
controller.jumpTo(50.0);
await tester.pump();
expect(controller.offset, equals(0.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 {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
......
......@@ -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(
null,
......
......@@ -96,7 +96,7 @@ void main() {
),
);
expect(log, equals(<int>[0, 1, 2]));
expect(log, equals(<int>[0, 1, 2, 3, 4]));
log.clear();
final ScrollableState state = tester.state(find.byType(Scrollable));
......@@ -106,7 +106,7 @@ void main() {
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[10, 11, 12, 13]));
expect(log, equals(<int>[8, 9, 10, 11, 12, 13, 14]));
log.clear();
position.jumpTo(975.0);
......@@ -114,7 +114,7 @@ void main() {
expect(log, isEmpty);
await tester.pump();
expect(log, equals(<int>[4, 5, 6, 7]));
expect(log, equals(<int>[7, 6, 5, 4, 3]));
log.clear();
});
......@@ -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 {
......@@ -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();
await tester.pumpWidget(
......@@ -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();
await tester.drag(find.byType(ListView), const Offset(0.0, -600.0));
......@@ -262,7 +262,7 @@ void main() {
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();
});
......
......@@ -38,7 +38,10 @@ void main() {
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();
testWidget.flip();
......@@ -50,7 +53,10 @@ void main() {
testWidget.flip();
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 {
......@@ -86,22 +92,34 @@ void main() {
await tester.pumpWidget(builder());
// 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();
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());
// 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();
await tester.pumpWidget(builder());
// 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();
});
......@@ -128,7 +146,7 @@ void main() {
child: new FlipWidget(
left: new ListView.builder(
scrollDirection: Axis.horizontal,
controller: new ScrollController(initialScrollOffset: 300.0),
controller: new ScrollController(initialScrollOffset: 500.0),
itemBuilder: itemBuilder,
),
right: const Text('Not Today'),
......@@ -139,23 +157,23 @@ void main() {
await tester.pumpWidget(builder());
// 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();
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());
// 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();
await tester.pumpWidget(builder());
// 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();
});
......@@ -189,18 +207,24 @@ void main() {
await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2]));
expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear();
tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2']));
expect(text, equals(<String>['0', '1', '2', '3']));
text.clear();
await tester.pumpWidget(builder());
expect(callbackTracker, equals(<int>[0, 1, 2]));
expect(callbackTracker, equals(<int>[
0, 1, 2,
3, // in cached area
]));
callbackTracker.clear();
tester.allWidgets.forEach(collectText);
expect(text, equals(<String>['0', '1', '2']));
expect(text, equals(<String>['0', '1', '2', '3']));
text.clear();
});
......@@ -308,9 +332,10 @@ void main() {
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\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'
' │ maxPaintExtent: 300.0)\n'
' │ maxPaintExtent: 300.0, cacheExtent: 300.0)\n'
' │ currently live children: 0 to 2\n'
' │\n'
' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
......
......@@ -339,7 +339,7 @@ void main() {
final Offset point1 = tester.getCenter(find.text('AA'));
await tester.dragFrom(point1, const Offset(0.0, 200.0));
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));
});
......@@ -646,4 +646,4 @@ class TestHeader extends SliverPersistentHeaderDelegate {
}
@override
bool shouldRebuild(TestHeader oldDelegate) => false;
}
\ No newline at end of file
}
......@@ -257,7 +257,7 @@ void main() {
),
));
expect(find.text('Alabama'), findsOneWidget);
expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
......
......@@ -2,6 +2,8 @@
// 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
......@@ -362,6 +364,12 @@ void main() {
label: r'item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'item 2',
),
],
),
],
......
......@@ -131,7 +131,7 @@ void main() {
);
// 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)
expect(find.text('Item $i'), findsOneWidget);
......
This diff is collapsed.
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