slivers_appbar_scrolling_test.dart 6.46 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 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

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

void main() {
19
  testWidgetsWithLeakTracking('Sliver appbars - scrolling', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
20 21
    GlobalKey key1, key2, key3, key4, key5;
    await tester.pumpWidget(
22
      Directionality(
23
        textDirection: TextDirection.ltr,
24
        child: CustomScrollView(
25
          slivers: <Widget>[
26 27 28 29 30
            BigSliver(key: key1 = GlobalKey()),
            SliverPersistentHeader(key: key2 = GlobalKey(), delegate: TestDelegate()),
            SliverPersistentHeader(key: key3 = GlobalKey(), delegate: TestDelegate()),
            BigSliver(key: key4 = GlobalKey()),
            BigSliver(key: key5 = GlobalKey()),
31 32
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
33 34
      ),
    );
35
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
36
    final double max = RenderBigSliver.height * 3.0 + TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
Ian Hickson's avatar
Ian Hickson committed
37 38 39 40 41
    assert(max < 10000.0);
    expect(max, 1450.0);
    expect(position.pixels, 0.0);
    expect(position.minScrollExtent, 0.0);
    expect(position.maxScrollExtent, max);
42
    position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(minutes: 1));
43
    await tester.pumpAndSettle(const Duration(milliseconds: 10));
Ian Hickson's avatar
Ian Hickson committed
44 45 46
    expect(position.pixels, max);
    expect(position.minScrollExtent, 0.0);
    expect(position.maxScrollExtent, max);
47 48 49 50
    verifyPaintPosition(key1, Offset.zero);
    verifyPaintPosition(key2, Offset.zero);
    verifyPaintPosition(key3, Offset.zero);
    verifyPaintPosition(key4, Offset.zero);
51
    verifyPaintPosition(key5, const Offset(0.0, 50.0));
Ian Hickson's avatar
Ian Hickson committed
52 53
  });

54
  testWidgetsWithLeakTracking('Sliver appbars - scrolling off screen', (WidgetTester tester) async {
55 56
    final GlobalKey key = GlobalKey();
    final TestDelegate delegate = TestDelegate();
Ian Hickson's avatar
Ian Hickson committed
57
    await tester.pumpWidget(
58
      Directionality(
59
        textDirection: TextDirection.ltr,
60
        child: CustomScrollView(
61 62
          slivers: <Widget>[
            const BigSliver(),
63
            SliverPersistentHeader(key: key, delegate: delegate),
64 65 66 67
            const BigSliver(),
            const BigSliver(),
          ],
        ),
Ian Hickson's avatar
Ian Hickson committed
68 69
      ),
    );
70
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
71
    position.animateTo(RenderBigSliver.height + delegate.maxExtent - 5.0, curve: Curves.linear, duration: const Duration(minutes: 1));
72
    await tester.pumpAndSettle(const Duration(milliseconds: 1000));
73
    final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container));
74
    final Rect rect = Rect.fromPoints(box.localToGlobal(Offset.zero), box.localToGlobal(box.size.bottomRight(Offset.zero)));
Dan Field's avatar
Dan Field committed
75
    expect(rect, equals(const Rect.fromLTWH(0.0, -195.0, 800.0, 200.0)));
Ian Hickson's avatar
Ian Hickson committed
76
  });
77

78
  testWidgetsWithLeakTracking('Sliver appbars - scrolling - overscroll gap is below header', (WidgetTester tester) async {
79 80 81 82 83 84 85
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: CustomScrollView(
          physics: const BouncingScrollPhysics(),
          slivers: <Widget>[
            SliverPersistentHeader(delegate: TestDelegate()),
86
            SliverList(
87
              delegate: SliverChildListDelegate(<Widget>[
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
                const SizedBox(
                  height: 300.0,
                  child: Text('X'),
                ),
              ]),
            ),
          ],
        ),
      ),
    );

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

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

    expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
    expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 250.0));
  });

110
  testWidgetsWithLeakTracking('Sliver appbars const child delegate - scrolling - overscroll gap is below header', (WidgetTester tester) async {
111 112 113 114 115 116 117 118 119
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: CustomScrollView(
          physics: const BouncingScrollPhysics(),
          slivers: <Widget>[
            SliverPersistentHeader(delegate: TestDelegate()),
            const SliverList(
              delegate: SliverChildListDelegate.fixed(<Widget>[
120
                SizedBox(
121
                  height: 300.0,
122
                  child: Text('X'),
123 124 125 126 127
                ),
              ]),
            ),
          ],
        ),
128 129 130
      ),
    );

131 132
    expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
    expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 200.0));
133

134
    final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
135 136 137
    position.jumpTo(-50.0);
    await tester.pump();

138 139
    expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
    expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 250.0));
140
  });
Ian Hickson's avatar
Ian Hickson committed
141 142
}

143
class TestDelegate extends SliverPersistentHeaderDelegate {
Ian Hickson's avatar
Ian Hickson committed
144 145 146 147
  @override
  double get maxExtent => 200.0;

  @override
148 149 150 151
  double get minExtent => 200.0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
152
    return Container(height: maxExtent);
Ian Hickson's avatar
Ian Hickson committed
153 154 155 156 157 158 159 160 161
  }

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


class RenderBigSliver extends RenderSliver {
  static const double height = 550.0;
162
  double get paintExtent => (height - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
Ian Hickson's avatar
Ian Hickson committed
163 164 165

  @override
  void performLayout() {
166
    geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
167 168 169 170 171 172 173 174
      scrollExtent: height,
      paintExtent: paintExtent,
      maxPaintExtent: height,
    );
  }
}

class BigSliver extends LeafRenderObjectWidget {
175
  const BigSliver({ super.key });
Ian Hickson's avatar
Ian Hickson committed
176 177
  @override
  RenderBigSliver createRenderObject(BuildContext context) {
178
    return RenderBigSliver();
Ian Hickson's avatar
Ian Hickson committed
179 180
  }
}