slivers_appbar_floating_test.dart 12.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

Ian Hickson's avatar
Ian Hickson committed
7 8
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
9
import 'package:flutter/widgets.dart';
Ian Hickson's avatar
Ian Hickson committed
10 11

void verifyPaintPosition(GlobalKey key, Offset ideal, bool visible) {
12
  final RenderSliver target = key.currentContext.findRenderObject() as RenderSliver;
Dan Field's avatar
Dan Field committed
13
  expect(target.parent, isA<RenderViewport>());
14
  final SliverPhysicalParentData parentData = target.parentData as SliverPhysicalParentData;
15
  final Offset actual = parentData.paintOffset;
Ian Hickson's avatar
Ian Hickson committed
16
  expect(actual, ideal);
17
  final SliverGeometry geometry = target.geometry;
Ian Hickson's avatar
Ian Hickson committed
18 19 20 21
  expect(geometry.visible, visible);
}

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

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

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

74
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
75 76
    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
77

78
    position.animateTo(bigHeight - 600.0 + delegate.maxExtent, curve: Curves.linear, duration: const Duration(minutes: 1));
79
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
80
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
81 82
    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));
83
    verifyPaintPosition(key3, const Offset(0.0, 600.0), false);
Ian Hickson's avatar
Ian Hickson committed
84 85

    assert(delegate.maxExtent * 2.0 < 600.0); // make sure this fits on the test screen...
86
    position.animateTo(bigHeight - 600.0 + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
87
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
88
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
89 90 91
    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
92

93
    position.animateTo(bigHeight, curve: Curves.linear, duration: const Duration(minutes: 1));
94
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
95 96
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
97 98
    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
99

100
    position.animateTo(bigHeight + delegate.maxExtent * 0.1, curve: Curves.linear, duration: const Duration(minutes: 1));
101
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
102 103
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
104 105
    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
106

107
    position.animateTo(bigHeight + delegate.maxExtent * 0.5, curve: Curves.linear, duration: const Duration(minutes: 1));
108
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
109 110
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
111 112
    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
113

114
    position.animateTo(bigHeight + delegate.maxExtent * 0.9, curve: Curves.linear, duration: const Duration(minutes: 1));
115
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
116 117
    verifyPaintPosition(key1, const Offset(0.0, 0.0), false);
    verifyPaintPosition(key2, const Offset(0.0, 0.0), true);
118 119
    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
120

121
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
122
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
123 124 125
    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
126 127 128
  });

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

146
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
147 148
    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
149

150
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
151
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
152 153 154
    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
155

156
    position.animateTo(bigHeight + delegate.maxExtent * 1.9, curve: Curves.linear, duration: const Duration(minutes: 1));
157
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
158 159 160
    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
161 162 163
  });

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

181
    verifyPaintPosition(key1, const Offset(0.0, 0.0), true);
182 183
    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
184

185
    position.animateTo(bigHeight + delegate.maxExtent * 2.0, curve: Curves.linear, duration: const Duration(minutes: 1));
186
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
187 188 189
    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
190

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

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

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

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

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

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

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

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
242
    return Container(constraints: BoxConstraints(minHeight: minExtent, maxHeight: maxExtent));
Ian Hickson's avatar
Ian Hickson committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  }

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

262
  double get paintExtent => (height - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent) as double;
Ian Hickson's avatar
Ian Hickson committed
263 264 265

  @override
  void performLayout() {
266
    geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
267 268 269 270 271 272 273 274
      scrollExtent: height,
      paintExtent: paintExtent,
      maxPaintExtent: height,
    );
  }
}

class BigSliver extends LeafRenderObjectWidget {
275
  const BigSliver({ Key key, this.height }) : super(key: key);
Ian Hickson's avatar
Ian Hickson committed
276 277 278 279 280

  final double height;

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

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