slivers_appbar_floating_test.dart 12.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1 2 3 4 5 6 7 8 9
// Copyright 2016 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_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

void verifyPaintPosition(GlobalKey key, Offset ideal, bool visible) {
10
  final RenderSliver target = key.currentContext.findRenderObject();
Adam Barth's avatar
Adam Barth committed
11
  expect(target.parent, const isInstanceOf<RenderViewport>());
12 13
  final SliverPhysicalParentData parentData = target.parentData;
  final Offset actual = parentData.paintOffset;
Ian Hickson's avatar
Ian Hickson committed
14
  expect(actual, ideal);
15
  final SliverGeometry geometry = target.geometry;
Ian Hickson's avatar
Ian Hickson committed
16 17 18 19
  expect(geometry.visible, visible);
}

void verifyActualBoxPosition(WidgetTester tester, Finder finder, int index, Rect ideal) {
20
  final RenderBox box = tester.renderObjectList<RenderBox>(finder).elementAt(index);
21
  final Rect rect = new Rect.fromPoints(box.localToGlobal(Offset.zero), box.localToGlobal(box.size.bottomRight(Offset.zero)));
Ian Hickson's avatar
Ian Hickson committed
22 23 24 25 26 27 28
  expect(rect, equals(ideal));
}

void main() {
  testWidgets('Sliver appbars - floating - scroll offset doesn\'t change', (WidgetTester tester) async {
    const double bigHeight = 1000.0;
    await tester.pumpWidget(
29 30 31 32 33 34 35 36 37
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          slivers: <Widget>[
            const BigSliver(height: bigHeight),
            new SliverPersistentHeader(delegate: new TestDelegate(), floating: true),
            const BigSliver(height: bigHeight),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
38 39
      ),
    );
40
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
Ian Hickson's avatar
Ian Hickson committed
41 42 43 44 45 46
    final double max = bigHeight * 2.0 + new TestDelegate().maxExtent - 600.0; // 600 is the height of the test viewport
    assert(max < 10000.0);
    expect(max, 1600.0);
    expect(position.pixels, 0.0);
    expect(position.minScrollExtent, 0.0);
    expect(position.maxScrollExtent, max);
47
    position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(minutes: 1));
48
    await tester.pumpAndSettle(const Duration(milliseconds: 50));
Ian Hickson's avatar
Ian Hickson committed
49 50 51 52 53 54 55 56 57 58
    expect(position.pixels, max);
    expect(position.minScrollExtent, 0.0);
    expect(position.maxScrollExtent, max);
  });

  testWidgets('Sliver appbars - floating - normal behavior works', (WidgetTester tester) async {
    final TestDelegate delegate = new TestDelegate();
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
59 60 61 62 63 64 65 66 67
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          slivers: <Widget>[
            new BigSliver(key: key1 = new GlobalKey(), height: bigHeight),
            new SliverPersistentHeader(key: key2 = new GlobalKey(), delegate: delegate, floating: true),
            new BigSliver(key: key3 = new GlobalKey(), height: bigHeight),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
68 69
      ),
    );
70
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
Ian Hickson's avatar
Ian Hickson committed
71

72 73 74
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
Ian Hickson's avatar
Ian Hickson committed
75

76
    position.animateTo(bigHeight - 600.0 + delegate.maxExtent, curve: Curves.linear, duration: const Duration(minutes: 1));
77
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
78
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
79 80
    verifyPaintPosition(key2, new Offset(0.0, 600.0 - delegate.maxExtent), true);
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, 600.0 - delegate.maxExtent, 800.0, delegate.maxExtent));
81
    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
Ian Hickson's avatar
Ian Hickson committed
82 83

    assert(delegate.maxExtent * 2.0 < 600.0); // make sure this fits on the test screen...
84
    position.animateTo(bigHeight - 600.0 + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
85
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
86
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
87 88 89 90
    verifyPaintPosition(key2, new Offset(0.0, 600.0 - delegate.maxExtent * 2.0), true);
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, 600.0 - delegate.maxExtent * 2.0, 800.0, delegate.maxExtent));
    verifyPaintPosition(key3, new Offset(0.0, 600.0 - delegate.maxExtent), true);

91
    position.animateTo(bigHeight, curve: Curves.linear, duration: const Duration(minutes: 1));
92
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
93 94
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
95 96 97
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent));
    verifyPaintPosition(key3, new Offset(0.0, delegate.maxExtent), true);

98
    position.animateTo(bigHeight + delegate.maxExtent * 0.1, curve: Curves.linear, duration: const Duration(minutes: 1));
99
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
100 101
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
102 103 104
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent * 0.9));
    verifyPaintPosition(key3, new Offset(0.0, delegate.maxExtent * 0.9), true);

105
    position.animateTo(bigHeight + delegate.maxExtent * 0.5, curve: Curves.linear, duration: const Duration(minutes: 1));
106
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
107 108
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
109 110 111
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent * 0.5));
    verifyPaintPosition(key3, new Offset(0.0, delegate.maxExtent * 0.5), true);

112
    position.animateTo(bigHeight + delegate.maxExtent * 0.9, curve: Curves.linear, duration: const Duration(minutes: 1));
113
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
114 115
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
116 117 118
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, -delegate.maxExtent * 0.4, 800.0, delegate.maxExtent * 0.5));
    verifyPaintPosition(key3, new Offset(0.0, delegate.maxExtent * 0.1), true);

119
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
120
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
121 122 123
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
124 125 126 127 128 129 130
  });

  testWidgets('Sliver appbars - floating - no floating behavior when animating', (WidgetTester tester) async {
    final TestDelegate delegate = new TestDelegate();
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
131 132 133 134 135 136 137 138 139
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          slivers: <Widget>[
            new BigSliver(key: key1 = new GlobalKey(), height: bigHeight),
            new SliverPersistentHeader(key: key2 = new GlobalKey(), delegate: delegate, floating: true),
            new BigSliver(key: key3 = new GlobalKey(), height: bigHeight),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
140 141
      ),
    );
142
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
Ian Hickson's avatar
Ian Hickson committed
143

144 145 146
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
Ian Hickson's avatar
Ian Hickson committed
147

148
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
149
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
150 151 152
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
153

154
    position.animateTo(bigHeight + delegate.maxExtent * 1.9, curve: Curves.linear, duration: const Duration(minutes: 1));
155
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
156 157 158
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
159 160 161 162 163 164 165
  });

  testWidgets('Sliver appbars - floating - floating behavior when dragging down', (WidgetTester tester) async {
    final TestDelegate delegate = new TestDelegate();
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
166 167 168 169 170 171 172 173 174
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          slivers: <Widget>[
            new BigSliver(key: key1 = new GlobalKey(), height: bigHeight),
            new SliverPersistentHeader(key: key2 = new GlobalKey(), delegate: delegate, floating: true),
            new BigSliver(key: key3 = new GlobalKey(), height: bigHeight),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
175 176
      ),
    );
177
    final ScrollPositionWithSingleContext position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
Ian Hickson's avatar
Ian Hickson committed
178

179 180 181
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
    verifyPaintPosition(key2, const Offset(0.0, 600.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
Ian Hickson's avatar
Ian Hickson committed
182

183
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
184
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
185 186 187
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
188

189
    position.animateTo(bigHeight + delegate.maxExtent * 1.9, curve: Curves.linear, duration: const Duration(minutes: 1));
190
    position.updateUserScrollDirection(ScrollDirection.forward); // ignore: invalid_use_of_protected_member
191
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
192 193
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
194
    verifyActualBoxPosition(tester, find.byType(Container), 0, new Rect.fromLTWH(0.0, -delegate.maxExtent * 0.4, 800.0, delegate.maxExtent * 0.5));
195
    verifyPaintPosition(key3, const Offset(0.0, 0.0), true);
Ian Hickson's avatar
Ian Hickson committed
196
  });
197 198 199

  testWidgets('Sliver appbars - floating - overscroll gap is below header', (WidgetTester tester) async {
    await tester.pumpWidget(
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          physics: const BouncingScrollPhysics(),
          slivers: <Widget>[
            new SliverPersistentHeader(delegate: new TestDelegate(), floating: true),
            new SliverList(
              delegate: new SliverChildListDelegate(<Widget>[
                const SizedBox(
                  height: 300.0,
                  child: const Text('X'),
                ),
              ]),
            ),
          ],
        ),
216 217 218
      ),
    );

219 220
    expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
    expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 200.0));
221

222
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
223 224 225
    position.jumpTo(-50.0);
    await tester.pump();

226 227
    expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
    expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 250.0));
228
  });
Ian Hickson's avatar
Ian Hickson committed
229 230
}

231
class TestDelegate extends SliverPersistentHeaderDelegate {
Ian Hickson's avatar
Ian Hickson committed
232 233 234 235
  @override
  double get maxExtent => 200.0;

  @override
236 237 238 239 240
  double get minExtent => 100.0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new Container(constraints: new BoxConstraints(minHeight: minExtent, maxHeight: maxExtent));
Ian Hickson's avatar
Ian Hickson committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
  }

  @override
  bool shouldRebuild(TestDelegate oldDelegate) => false;
}


class RenderBigSliver extends RenderSliver {
  RenderBigSliver(double height) : _height = height;

  double get height => _height;
  double _height;
  set height(double value) {
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

  double get paintExtent => (height - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);

  @override
  void performLayout() {
    geometry = new SliverGeometry(
      scrollExtent: height,
      paintExtent: paintExtent,
      maxPaintExtent: height,
    );
  }
}

class BigSliver extends LeafRenderObjectWidget {
273
  const BigSliver({ Key key, this.height }) : super(key: key);
Ian Hickson's avatar
Ian Hickson committed
274 275 276 277 278 279 280 281 282 283 284 285 286

  final double height;

  @override
  RenderBigSliver createRenderObject(BuildContext context) {
    return new RenderBigSliver(height);
  }

  @override
  void updateRenderObject(BuildContext context, RenderBigSliver renderObject) {
    renderObject.height = height;
  }
}