refresh_indicator_test.dart 13.5 KB
Newer Older
1 2 3 4 5 6
// Copyright 2015 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:async';

7
import 'package:flutter/foundation.dart';
8 9 10
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

11
bool refreshCalled = false;
12

13
Future<void> refresh() {
14
  refreshCalled = true;
15
  return new Future<void>.value();
16
}
17

18
Future<void> holdRefresh() {
19
  refreshCalled = true;
20
  return new Completer<void>().future;
21 22 23
}

void main() {
24
  testWidgets('RefreshIndicator', (WidgetTester tester) async {
25
    refreshCalled = false;
26
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
27 28
      new MaterialApp(
        home: new RefreshIndicator(
29 30 31 32 33 34 35 36 37 38
          onRefresh: refresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
            children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map((String item) {
              return new SizedBox(
                height: 200.0,
                child: new Text(item),
              );
            }).toList(),
          ),
39 40
        ),
      ),
41
    );
42

43
    await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0);
44 45 46 47
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
48
    expect(refreshCalled, true);
49
  });
50

51 52 53 54 55 56 57 58 59 60 61
  testWidgets('Refresh Indicator - nested', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
      new MaterialApp(
        home: new RefreshIndicator(
          notificationPredicate: (ScrollNotification notification) => notification.depth == 1,
          onRefresh: refresh,
          child: new SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: new Container(
              width: 600.0,
62
              child: new ListView(
63 64 65 66 67 68 69 70 71 72 73 74 75
                physics: const AlwaysScrollableScrollPhysics(),
                children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map((String item) {
                  return new SizedBox(
                    height: 200.0,
                    child: new Text(item),
                  );
                }).toList(),
              ),
            ),
          ),
        ),
      ),
    );
76

77 78 79 80 81
    await tester.fling(find.text('A'), const Offset(300.0, 0.0), 1000.0); // horizontal fling
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
82 83
    expect(refreshCalled, false);

84 85 86 87 88 89

    await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0); // vertical fling
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
90
    expect(refreshCalled, true);
91
  });
92 93 94 95

  testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
96 97
      new MaterialApp(
        home: new RefreshIndicator(
98 99 100 101
          onRefresh: refresh,
          child: new ListView(
            reverse: true,
            physics: const AlwaysScrollableScrollPhysics(),
102
            children: const <Widget>[
103
              SizedBox(
104
                height: 200.0,
105
                child: Text('X'),
106 107 108
              ),
            ],
          ),
109 110 111 112 113 114 115 116 117 118 119 120
        ),
      ),
    );

    await tester.fling(find.text('X'), const Offset(0.0, -300.0), 1000.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator settle animation
    await tester.pump(const Duration(seconds: 1)); // finish the indicator hide animation
    expect(refreshCalled, true);
  });

121 122 123
  testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
124 125
      new MaterialApp(
        home: new RefreshIndicator(
126 127 128
          onRefresh: holdRefresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
129
            children: const <Widget>[
130
              SizedBox(
131
                height: 200.0,
132
                child: Text('X'),
133 134 135
              ),
            ],
          ),
136 137 138 139
        ),
      ),
    );

140
    await tester.fling(find.text('X'), const Offset(0.0, 300.0), 1000.0);
141 142 143
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
144
    expect(tester.getCenter(find.byType(RefreshProgressIndicator)).dy, lessThan(300.0));
145 146 147 148 149
  });

  testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
150 151
      new MaterialApp(
        home: new RefreshIndicator(
152 153 154 155
          onRefresh: holdRefresh,
          child: new ListView(
            reverse: true,
            physics: const AlwaysScrollableScrollPhysics(),
156
            children: const <Widget>[
157
              SizedBox(
158
                height: 200.0,
159
                child: Text('X'),
160 161 162
              ),
            ],
          ),
163 164 165 166 167 168 169 170
        ),
      ),
    );

    await tester.fling(find.text('X'), const Offset(0.0, -300.0), 1000.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
171
    expect(tester.getCenter(find.byType(RefreshProgressIndicator)).dy, greaterThan(300.0));
172 173 174 175 176
  });

  testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
177 178
      new MaterialApp(
        home: new RefreshIndicator(
179 180 181
          onRefresh: refresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
182
            children: const <Widget>[
183
              SizedBox(
184
                height: 200.0,
185
                child: Text('X'),
186 187 188
              ),
            ],
          ),
189 190 191 192 193 194 195 196 197 198 199 200 201
        ),
      ),
    );

    // this fling is horizontal, not up or down
    await tester.fling(find.text('X'), const Offset(1.0, 0.0), 1000.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, false);
  });

202 203 204
  testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
205 206
      new MaterialApp(
        home: new RefreshIndicator(
207 208 209
          onRefresh: refresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
210
            children: const <Widget>[
211
              SizedBox(
212
                height: 200.0,
213
                child: Text('X'),
214 215 216
              ),
            ],
          ),
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
        ),
      ),
    );

    await tester.fling(find.text('X'), const Offset(0.0, 100.0), 1000.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, false);
  });

  testWidgets('RefreshIndicator - show - slow', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
232 233
      new MaterialApp(
        home: new RefreshIndicator(
234 235 236
          onRefresh: holdRefresh, // this one never returns
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
237
            children: const <Widget>[
238
              SizedBox(
239
                height: 200.0,
240
                child: Text('X'),
241 242 243
              ),
            ],
          ),
244 245 246 247 248 249 250
        ),
      ),
    );

    bool completed = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
251
      .then<void>((void value) { completed = true; });
252 253 254 255 256 257 258 259 260 261 262
    await tester.pump();
    expect(completed, false);
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, true);
    expect(completed, false);
    completed = false;
    refreshCalled = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
263
      .then<void>((void value) { completed = true; });
264 265 266 267 268 269 270 271 272 273 274
    await tester.pump();
    expect(completed, false);
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, false);
  });

  testWidgets('RefreshIndicator - show - fast', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
275 276
      new MaterialApp(
        home: new RefreshIndicator(
277 278 279
          onRefresh: refresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
280
            children: const <Widget>[
281
              SizedBox(
282
                height: 200.0,
283
                child: Text('X'),
284 285 286
              ),
            ],
          ),
287 288 289 290 291 292 293
        ),
      ),
    );

    bool completed = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
294
      .then<void>((void value) { completed = true; });
295 296 297 298 299 300 301 302 303 304 305
    await tester.pump();
    expect(completed, false);
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, true);
    expect(completed, true);
    completed = false;
    refreshCalled = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
306
      .then<void>((void value) { completed = true; });
307 308 309 310 311 312 313 314 315 316 317 318
    await tester.pump();
    expect(completed, false);
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, true);
    expect(completed, true);
  });

  testWidgets('RefreshIndicator - show - fast - twice', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
319 320
      new MaterialApp(
        home: new RefreshIndicator(
321 322 323
          onRefresh: refresh,
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
324
            children: const <Widget>[
325
              SizedBox(
326
                height: 200.0,
327
                child: Text('X'),
328 329 330
              ),
            ],
          ),
331 332 333 334 335 336 337
        ),
      ),
    );

    bool completed1 = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
338
      .then<void>((void value) { completed1 = true; });
339 340 341
    bool completed2 = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
342
      .then<void>((void value) { completed2 = true; });
343 344 345 346 347 348 349 350 351 352
    await tester.pump();
    expect(completed1, false);
    expect(completed2, false);
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    expect(refreshCalled, true);
    expect(completed1, true);
    expect(completed2, true);
  });
353 354 355 356 357 358 359 360

  testWidgets('RefreshIndicator - onRefresh asserts', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
      new MaterialApp(
        home: new RefreshIndicator(
          onRefresh: () {
            refreshCalled = true;
361
            return null; // Missing a returned Future value here, should cause framework to throw.
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
          },
          child: new ListView(
            physics: const AlwaysScrollableScrollPhysics(),
            children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map((String item) {
              return new SizedBox(
                height: 200.0,
                child: new Text(item),
              );
            }).toList(),
          ),
        ),
      ),
    );

    await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the scroll animation
    expect(refreshCalled, true);
    expect(tester.takeException(), isFlutterError);
  });
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418

  testWidgets('Refresh starts while scroll view moves back to 0.0 after overscroll on iOS', (WidgetTester tester) async {
    debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
    refreshCalled = false;
    double lastScrollOffset;
    final ScrollController controller = new ScrollController();

    await tester.pumpWidget(
      new MaterialApp(
        home: new RefreshIndicator(
          onRefresh: refresh,
          child: new ListView(
            controller: controller,
            physics: const AlwaysScrollableScrollPhysics(),
            children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map((String item) {
              return new SizedBox(
                height: 200.0,
                child: new Text(item),
              );
            }).toList(),
          ),
        ),
      ),
    );

    await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0);
    await tester.pump(const Duration(milliseconds: 100));
    expect(lastScrollOffset = controller.offset, lessThan(0.0));
    expect(refreshCalled, isFalse);

    await tester.pump(const Duration(milliseconds: 100));
    expect(controller.offset, greaterThan(lastScrollOffset));
    expect(controller.offset, lessThan(0.0));
    expect(refreshCalled, isTrue);

    debugDefaultTargetPlatformOverride = null;
  });
419
}