overscroll_indicator_test.dart 12.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math' as math;

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';

import '../rendering/mock_canvas.dart';

final Matcher doesNotOverscroll = isNot(paints..circle());

15
Future<void> slowDrag(WidgetTester tester, Offset start, Offset offset) async {
16
  final TestGesture gesture = await tester.startGesture(start);
17 18 19 20 21 22 23 24 25 26
  for (int index = 0; index < 10; index += 1) {
    await gesture.moveBy(offset);
    await tester.pump(const Duration(milliseconds: 20));
  }
  await gesture.up();
}

void main() {
  testWidgets('Overscroll indicator color', (WidgetTester tester) async {
    await tester.pumpWidget(
27
      const Directionality(
28
        textDirection: TextDirection.ltr,
29
        child: CustomScrollView(
30
          slivers: <Widget>[
31
            SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
32 33
          ],
        ),
34 35
      ),
    );
36
    final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
37 38 39 40

    expect(painter, doesNotOverscroll);

    // the scroll gesture from tester.scroll happens in zero time, so nothing should appear:
41
    await tester.drag(find.byType(Scrollable), const Offset(0.0, 100.0));
42 43 44 45 46 47
    expect(painter, doesNotOverscroll);
    await tester.pump(); // allow the ticker to register itself
    expect(painter, doesNotOverscroll);
    await tester.pump(const Duration(milliseconds: 100)); // animate
    expect(painter, doesNotOverscroll);

48
    final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
49 50 51 52 53
    await tester.pump(const Duration(milliseconds: 100)); // animate
    expect(painter, doesNotOverscroll);
    await gesture.up();
    expect(painter, doesNotOverscroll);

54
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
55 56
    expect(painter, paints..circle(color: const Color(0x0DFFFFFF)));

57
    await tester.pumpAndSettle(const Duration(seconds: 1));
58 59
    expect(painter, doesNotOverscroll);
  });
60

61 62
  testWidgets('Nested scrollable', (WidgetTester tester) async {
    await tester.pumpWidget(
63
      Directionality(
64
        textDirection: TextDirection.ltr,
65
        child: GlowingOverscrollIndicator(
66 67 68
          axisDirection: AxisDirection.down,
          color: const Color(0x0DFFFFFF),
          notificationPredicate: (ScrollNotification notification) => notification.depth == 1,
69
          child: SingleChildScrollView(
70
            scrollDirection: Axis.horizontal,
71
            child: Container(
72
                width: 600.0,
73 74
                child: const CustomScrollView(
                  slivers: <Widget>[
75
                    SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
76 77 78 79 80 81 82
                  ],
                ),
              ),
            ),
          ),
        ),
      );
83

84 85
    final RenderObject outerPainter = tester.renderObject(find.byType(CustomPaint).first);
    final RenderObject innerPainter = tester.renderObject(find.byType(CustomPaint).last);
86

87 88 89 90
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
    expect(outerPainter, paints..circle());
    expect(innerPainter, paints..circle());
  });
91

92 93
  testWidgets('Overscroll indicator changes side when you drag on the other side', (WidgetTester tester) async {
    await tester.pumpWidget(
94
      const Directionality(
95
        textDirection: TextDirection.ltr,
96
        child: CustomScrollView(
97
          slivers: <Widget>[
98
            SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
99 100
          ],
        ),
101 102
      ),
    );
103
    final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
104

105
    await slowDrag(tester, const Offset(400.0, 200.0), const Offset(0.0, 10.0));
106
    expect(painter, paints..circle(x: 400.0));
107
    await slowDrag(tester, const Offset(100.0, 200.0), const Offset(0.0, 10.0));
108 109 110
    expect(painter, paints..something((Symbol method, List<dynamic> arguments) {
      if (method != #drawCircle)
        return false;
111
      final Offset center = arguments[0] as Offset;
112
      if (center.dx < 400.0)
113 114 115
        return true;
      throw 'Dragging on left hand side did not overscroll on left hand side.';
    }));
116
    await slowDrag(tester, const Offset(700.0, 200.0), const Offset(0.0, 10.0));
117 118 119
    expect(painter, paints..something((Symbol method, List<dynamic> arguments) {
      if (method != #drawCircle)
        return false;
120
      final Offset center = arguments[0] as Offset;
121
      if (center.dx > 400.0)
122 123 124 125
        return true;
      throw 'Dragging on right hand side did not overscroll on right hand side.';
    }));

126
    await tester.pumpAndSettle(const Duration(seconds: 1));
127 128 129 130 131
    expect(painter, doesNotOverscroll);
  });

  testWidgets('Overscroll indicator changes side when you shift sides', (WidgetTester tester) async {
    await tester.pumpWidget(
132
      const Directionality(
133
        textDirection: TextDirection.ltr,
134
        child: CustomScrollView(
135
          slivers: <Widget>[
136
            SliverToBoxAdapter(child: SizedBox(height: 2000.0)),
137 138
          ],
        ),
139 140
      ),
    );
141
    final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
142
    final TestGesture gesture = await tester.startGesture(const Offset(300.0, 200.0));
143 144 145 146 147 148 149 150 151
    await gesture.moveBy(const Offset(0.0, 10.0));
    await tester.pump(const Duration(milliseconds: 20));
    double oldX = 0.0;
    for (int index = 0; index < 10; index += 1) {
      await gesture.moveBy(const Offset(50.0, 50.0));
      await tester.pump(const Duration(milliseconds: 20));
      expect(painter, paints..something((Symbol method, List<dynamic> arguments) {
        if (method != #drawCircle)
          return false;
152
        final Offset center = arguments[0] as Offset;
153
        if (center.dx <= oldX)
154
          throw 'Sliding to the right did not make the center of the radius slide to the right.';
155
        oldX = center.dx;
156 157 158 159 160
        return true;
      }));
    }
    await gesture.up();

161
    await tester.pumpAndSettle(const Duration(seconds: 1));
162 163 164 165 166 167
    expect(painter, doesNotOverscroll);
  });

  group('Flipping direction of scrollable doesn\'t change overscroll behavior', () {
    testWidgets('down', (WidgetTester tester) async {
      await tester.pumpWidget(
168
        const Directionality(
169
          textDirection: TextDirection.ltr,
170
          child: CustomScrollView(
171 172
            physics: AlwaysScrollableScrollPhysics(),
            slivers: <Widget>[
173
              SliverToBoxAdapter(child: SizedBox(height: 20.0)),
174 175
            ],
          ),
176 177
        ),
      );
178
      final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
179
      await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
180 181
      expect(painter, paints..save()..circle()..restore()..save()..scale(y: -1.0)..restore()..restore());

182
      await tester.pumpAndSettle(const Duration(seconds: 1));
183 184 185 186 187
      expect(painter, doesNotOverscroll);
    });

    testWidgets('up', (WidgetTester tester) async {
      await tester.pumpWidget(
188
        const Directionality(
189
          textDirection: TextDirection.ltr,
190
          child: CustomScrollView(
191
            reverse: true,
192 193
            physics: AlwaysScrollableScrollPhysics(),
            slivers: <Widget>[
194
              SliverToBoxAdapter(child: SizedBox(height: 20.0)),
195 196
            ],
          ),
197 198
        ),
      );
199
      final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
200
      await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
201 202
      expect(painter, paints..save()..scale(y: -1.0)..restore()..save()..circle()..restore()..restore());

203
      await tester.pumpAndSettle(const Duration(seconds: 1));
204 205 206 207 208 209
      expect(painter, doesNotOverscroll);
    });
  });

  testWidgets('Overscroll in both directions', (WidgetTester tester) async {
    await tester.pumpWidget(
210
      const Directionality(
211
        textDirection: TextDirection.ltr,
212
        child: CustomScrollView(
213 214
          physics: AlwaysScrollableScrollPhysics(),
          slivers: <Widget>[
215
            SliverToBoxAdapter(child: SizedBox(height: 20.0)),
216 217
          ],
        ),
218 219
      ),
    );
220
    final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
221
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
222 223
    expect(painter, paints..circle());
    expect(painter, isNot(paints..circle()..circle()));
224
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, -5.0));
225 226
    expect(painter, paints..circle()..circle());

227
    await tester.pumpAndSettle(const Duration(seconds: 1));
228 229 230 231
    expect(painter, doesNotOverscroll);
  });

  testWidgets('Overscroll horizontally', (WidgetTester tester) async {
232
    await tester.pumpWidget(
233
      const Directionality(
234
        textDirection: TextDirection.ltr,
235
        child: CustomScrollView(
236
          scrollDirection: Axis.horizontal,
237 238
          physics: AlwaysScrollableScrollPhysics(),
          slivers: <Widget>[
239
            SliverToBoxAdapter(child: SizedBox(height: 20.0)),
240 241
          ],
        ),
242
      ),
243
    );
244
    final RenderObject painter = tester.renderObject(find.byType(CustomPaint));
245
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(5.0, 0.0));
246
    expect(painter, paints..rotate(angle: math.pi / 2.0)..circle()..saveRestore());
247
    expect(painter, isNot(paints..circle()..circle()));
248
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(-5.0, 0.0));
249 250
    expect(painter, paints..rotate(angle: math.pi / 2.0)..circle()
                          ..rotate(angle: math.pi / 2.0)..circle());
251

252
    await tester.pumpAndSettle(const Duration(seconds: 1));
253 254 255
    expect(painter, doesNotOverscroll);
  });

256
  testWidgets('Nested overscrolls do not throw exceptions', (WidgetTester tester) async {
257
    await tester.pumpWidget(Directionality(
258
      textDirection: TextDirection.ltr,
259
      child: PageView(
260
        children: <Widget>[
261
          ListView(
262
            children: <Widget>[
263
              Container(
264 265
                width: 2000.0,
                height: 2000.0,
266
                color: const Color(0xFF00FF00),
267 268 269 270 271
              ),
            ],
          ),
        ],
      ),
272
    ));
273

274
    await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 2000.0));
275
    await tester.pumpAndSettle();
276 277
  });

278 279 280
  testWidgets('Changing settings', (WidgetTester tester) async {
    RenderObject painter;

281
    await tester.pumpWidget(
282
      Directionality(
283
        textDirection: TextDirection.ltr,
284 285
        child: ScrollConfiguration(
          behavior: TestScrollBehavior1(),
286
          child: const CustomScrollView(
287
            scrollDirection: Axis.horizontal,
288
            physics: AlwaysScrollableScrollPhysics(),
289
            reverse: true,
290
            slivers: <Widget>[
291
              SliverToBoxAdapter(child: SizedBox(height: 20.0)),
292 293
            ],
          ),
294
        ),
295
      ),
296
    );
297
    painter = tester.renderObject(find.byType(CustomPaint));
298
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(5.0, 0.0));
299
    expect(painter, paints..rotate(angle: math.pi / 2.0)..circle(color: const Color(0x0A00FF00)));
300 301
    expect(painter, isNot(paints..circle()..circle()));

302
    await tester.pumpAndSettle(const Duration(seconds: 1));
303
    await tester.pumpWidget(
304
      Directionality(
305
        textDirection: TextDirection.ltr,
306 307
        child: ScrollConfiguration(
          behavior: TestScrollBehavior2(),
308
          child: const CustomScrollView(
309
            scrollDirection: Axis.horizontal,
310 311
            physics: AlwaysScrollableScrollPhysics(),
            slivers: <Widget>[
312
              SliverToBoxAdapter(child: SizedBox(height: 20.0)),
313 314
            ],
          ),
315
        ),
316
      ),
317
    );
318
    painter = tester.renderObject(find.byType(CustomPaint));
319
    await slowDrag(tester, const Offset(200.0, 200.0), const Offset(5.0, 0.0));
320
    expect(painter, paints..rotate(angle: math.pi / 2.0)..circle(color: const Color(0x0A0000FF))..saveRestore());
321 322 323 324
    expect(painter, isNot(paints..circle()..circle()));
  });
}

Adam Barth's avatar
Adam Barth committed
325
class TestScrollBehavior1 extends ScrollBehavior {
326
  @override
327
  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
328
    return GlowingOverscrollIndicator(
329 330 331 332
      child: child,
      axisDirection: axisDirection,
      color: const Color(0xFF00FF00),
    );
333 334 335
  }
}

Adam Barth's avatar
Adam Barth committed
336
class TestScrollBehavior2 extends ScrollBehavior {
337
  @override
338
  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
339
    return GlowingOverscrollIndicator(
340 341 342 343
      child: child,
      axisDirection: axisDirection,
      color: const Color(0xFF0000FF),
    );
344 345
  }
}