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

void verifyPaintPosition(GlobalKey key, Offset ideal, bool visible) {
10
  final RenderSliver target = key.currentContext.findRenderObject();
11
  expect(target.parent, 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 = 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
      Directionality(
30
        textDirection: TextDirection.ltr,
31
        child: CustomScrollView(
32 33
          slivers: <Widget>[
            const BigSliver(height: bigHeight),
34
            SliverPersistentHeader(delegate: TestDelegate(), floating: true),
35 36 37
            const BigSliver(height: bigHeight),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
38 39
      ),
    );
40
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
41
    final double max = bigHeight * 2.0 + TestDelegate().maxExtent - 600.0; // 600 is the height of the test viewport
Ian Hickson's avatar
Ian Hickson committed
42 43 44 45 46
    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
    expect(position.pixels, max);
    expect(position.minScrollExtent, 0.0);
    expect(position.maxScrollExtent, max);
  });

  testWidgets('Sliver appbars - floating - normal behavior works', (WidgetTester tester) async {
55
    final TestDelegate delegate = TestDelegate();
Ian Hickson's avatar
Ian Hickson committed
56 57 58
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
59
      Directionality(
60
        textDirection: TextDirection.ltr,
61
        child: CustomScrollView(
62
          slivers: <Widget>[
63 64 65
            BigSliver(key: key1 = GlobalKey(), height: bigHeight),
            SliverPersistentHeader(key: key2 = GlobalKey(), delegate: delegate, floating: true),
            BigSliver(key: key3 = GlobalKey(), height: bigHeight),
66 67
          ],
        ),
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
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
73 74
    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 1200.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);
79 80
    verifyPaintPosition(key2, Offset(0.0, 600.0 - delegate.maxExtent), true);
    verifyActualBoxPosition(tester, find.byType(Container), 0, 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);
87 88 89
    verifyPaintPosition(key2, Offset(0.0, 600.0 - delegate.maxExtent * 2.0), true);
    verifyActualBoxPosition(tester, find.byType(Container), 0, Rect.fromLTWH(0.0, 600.0 - delegate.maxExtent * 2.0, 800.0, delegate.maxExtent));
    verifyPaintPosition(key3, Offset(0.0, 600.0 - delegate.maxExtent), true);
Ian Hickson's avatar
Ian Hickson committed
90

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);
95 96
    verifyActualBoxPosition(tester, find.byType(Container), 0, Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent));
    verifyPaintPosition(key3, Offset(0.0, delegate.maxExtent), true);
Ian Hickson's avatar
Ian Hickson committed
97

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);
102 103
    verifyActualBoxPosition(tester, find.byType(Container), 0, Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent * 0.9));
    verifyPaintPosition(key3, Offset(0.0, delegate.maxExtent * 0.9), true);
Ian Hickson's avatar
Ian Hickson committed
104

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);
109 110
    verifyActualBoxPosition(tester, find.byType(Container), 0, Rect.fromLTWH(0.0, 0.0, 800.0, delegate.maxExtent * 0.5));
    verifyPaintPosition(key3, Offset(0.0, delegate.maxExtent * 0.5), true);
Ian Hickson's avatar
Ian Hickson committed
111

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);
116 117
    verifyActualBoxPosition(tester, find.byType(Container), 0, Rect.fromLTWH(0.0, -delegate.maxExtent * 0.4, 800.0, delegate.maxExtent * 0.5));
    verifyPaintPosition(key3, Offset(0.0, delegate.maxExtent * 0.1), true);
Ian Hickson's avatar
Ian Hickson committed
118

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
  });

  testWidgets('Sliver appbars - floating - no floating behavior when animating', (WidgetTester tester) async {
127
    final TestDelegate delegate = TestDelegate();
Ian Hickson's avatar
Ian Hickson committed
128 129 130
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
131
      Directionality(
132
        textDirection: TextDirection.ltr,
133
        child: CustomScrollView(
134
          slivers: <Widget>[
135 136 137
            BigSliver(key: key1 = GlobalKey(), height: bigHeight),
            SliverPersistentHeader(key: key2 = GlobalKey(), delegate: delegate, floating: true),
            BigSliver(key: key3 = GlobalKey(), height: bigHeight),
138 139
          ],
        ),
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
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
145 146
    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 1200.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
  });

  testWidgets('Sliver appbars - floating - floating behavior when dragging down', (WidgetTester tester) async {
162
    final TestDelegate delegate = TestDelegate();
Ian Hickson's avatar
Ian Hickson committed
163 164 165
    const double bigHeight = 1000.0;
    GlobalKey key1, key2, key3;
    await tester.pumpWidget(
166
      Directionality(
167
        textDirection: TextDirection.ltr,
168
        child: CustomScrollView(
169
          slivers: <Widget>[
170 171 172
            BigSliver(key: key1 = GlobalKey(), height: bigHeight),
            SliverPersistentHeader(key: key2 = GlobalKey(), delegate: delegate, floating: true),
            BigSliver(key: key3 = GlobalKey(), height: bigHeight),
173 174
          ],
        ),
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
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
180 181
    verifyPaintPosition(key2, const Offset(0.0, 1000.0), false);
    verifyPaintPosition(key3, const Offset(0.0, 1200.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);
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);
194
    verifyActualBoxPosition(tester, find.byType(Container), 0, 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
      Directionality(
201
        textDirection: TextDirection.ltr,
202
        child: CustomScrollView(
203 204
          physics: const BouncingScrollPhysics(),
          slivers: <Widget>[
205
            SliverPersistentHeader(delegate: TestDelegate(), floating: true),
206
            const SliverList(
207
              delegate: SliverChildListDelegate(<Widget>[
208
                SizedBox(
209
                  height: 300.0,
210
                  child: Text('X'),
211 212 213 214 215
                ),
              ]),
            ),
          ],
        ),
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
  double get minExtent => 100.0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
240
    return Container(constraints: 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
  }

  @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() {
264
    geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
265 266 267 268 269 270 271 272
      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

  final double height;

  @override
  RenderBigSliver createRenderObject(BuildContext context) {
279
    return RenderBigSliver(height);
Ian Hickson's avatar
Ian Hickson committed
280 281 282 283 284 285 286
  }

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