overscroll_indicator_test.dart 12.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright 2017 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 '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<Null> 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 28 29
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
30
          slivers: const <Widget>[
31
            const SliverToBoxAdapter(child: const 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 63 64 65 66 67 68 69 70 71 72 73
  testWidgets('Nested scrollable', (WidgetTester tester) async {
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new GlowingOverscrollIndicator(
          axisDirection: AxisDirection.down,
          color: const Color(0x0DFFFFFF),
          notificationPredicate: (ScrollNotification notification) => notification.depth == 1,
          child: new SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: new Container(
                width: 600.0,
                child: new CustomScrollView(
74
                  slivers: const <Widget>[
75
                      const SliverToBoxAdapter(child: const 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 95 96
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
97
          slivers: const <Widget>[
98
            const SliverToBoxAdapter(child: const 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 112
      final Offset center = arguments[0];
      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 121
      final Offset center = arguments[0];
      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 133 134
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
135
          slivers: const <Widget>[
136
            const SliverToBoxAdapter(child: const 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 153
        final Offset center = arguments[0];
        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 169 170 171
        new Directionality(
          textDirection: TextDirection.ltr,
          child: new CustomScrollView(
            physics: const AlwaysScrollableScrollPhysics(),
172
            slivers: const <Widget>[
173
              const SliverToBoxAdapter(child: const 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 189 190 191 192
        new Directionality(
          textDirection: TextDirection.ltr,
          child: new CustomScrollView(
            reverse: true,
            physics: const AlwaysScrollableScrollPhysics(),
193
            slivers: const <Widget>[
194
              const SliverToBoxAdapter(child: const 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 211 212 213
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
214
          slivers: const <Widget>[
215
            const SliverToBoxAdapter(child: const 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 233 234 235 236 237
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          scrollDirection: Axis.horizontal,
          physics: const AlwaysScrollableScrollPhysics(),
238
          slivers: const <Widget>[
239
            const SliverToBoxAdapter(child: const 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 258 259
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new PageView(
260 261 262 263 264 265
        children: <Widget>[
          new ListView(
            children: <Widget>[
              new Container(
                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 282 283 284 285 286 287 288 289
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new ScrollConfiguration(
          behavior: new TestScrollBehavior1(),
          child: new CustomScrollView(
            scrollDirection: Axis.horizontal,
            physics: const AlwaysScrollableScrollPhysics(),
            reverse: true,
290
            slivers: const <Widget>[
291
              const SliverToBoxAdapter(child: const 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 304 305 306 307 308 309 310
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new ScrollConfiguration(
          behavior: new TestScrollBehavior2(),
          child: new CustomScrollView(
            scrollDirection: Axis.horizontal,
            physics: const AlwaysScrollableScrollPhysics(),
311
            slivers: const <Widget>[
312
              const SliverToBoxAdapter(child: const 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 328 329 330 331 332
  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
    return new GlowingOverscrollIndicator(
      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 339 340 341 342 343
  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
    return new GlowingOverscrollIndicator(
      child: child,
      axisDirection: axisDirection,
      color: const Color(0xFF0000FF),
    );
344 345
  }
}