refresh_indicator_test.dart 13.6 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 Future<void>.value();
16
}
17

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

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

44
    await tester.fling(find.text('A'), const Offset(0.0, 300.0), 1000.0);
45
    await tester.pump();
46 47 48 49 50

    expect(tester.getSemantics(find.byType(RefreshProgressIndicator)), matchesSemantics(
      label: 'Refresh',
    ));

51 52 53
    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
54
    expect(refreshCalled, true);
55
    handle.dispose();
56
  });
57

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

84 85 86 87 88
    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
89 90
    expect(refreshCalled, false);

91 92 93 94 95 96

    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
97
    expect(refreshCalled, true);
98
  });
99 100 101 102

  testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
103 104
      MaterialApp(
        home: RefreshIndicator(
105
          onRefresh: refresh,
106
          child: ListView(
107 108
            reverse: true,
            physics: const AlwaysScrollableScrollPhysics(),
109
            children: const <Widget>[
110
              SizedBox(
111
                height: 200.0,
112
                child: Text('X'),
113 114 115
              ),
            ],
          ),
116 117 118 119 120 121 122 123 124 125 126 127
        ),
      ),
    );

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

128 129 130
  testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
131 132
      MaterialApp(
        home: RefreshIndicator(
133
          onRefresh: holdRefresh,
134
          child: ListView(
135
            physics: const AlwaysScrollableScrollPhysics(),
136
            children: const <Widget>[
137
              SizedBox(
138
                height: 200.0,
139
                child: Text('X'),
140 141 142
              ),
            ],
          ),
143 144 145 146
        ),
      ),
    );

147
    await tester.fling(find.text('X'), const Offset(0.0, 300.0), 1000.0);
148 149 150
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
151
    expect(tester.getCenter(find.byType(RefreshProgressIndicator)).dy, lessThan(300.0));
152 153 154 155 156
  });

  testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
157 158
      MaterialApp(
        home: RefreshIndicator(
159
          onRefresh: holdRefresh,
160
          child: ListView(
161 162
            reverse: true,
            physics: const AlwaysScrollableScrollPhysics(),
163
            children: const <Widget>[
164
              SizedBox(
165
                height: 200.0,
166
                child: Text('X'),
167 168 169
              ),
            ],
          ),
170 171 172 173 174 175 176 177
        ),
      ),
    );

    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));
178
    expect(tester.getCenter(find.byType(RefreshProgressIndicator)).dy, greaterThan(300.0));
179 180 181 182 183
  });

  testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
184 185
      MaterialApp(
        home: RefreshIndicator(
186
          onRefresh: refresh,
187
          child: ListView(
188
            physics: const AlwaysScrollableScrollPhysics(),
189
            children: const <Widget>[
190
              SizedBox(
191
                height: 200.0,
192
                child: Text('X'),
193 194 195
              ),
            ],
          ),
196 197 198 199 200 201 202 203 204 205 206 207 208
        ),
      ),
    );

    // 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);
  });

209 210 211
  testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
212 213
      MaterialApp(
        home: RefreshIndicator(
214
          onRefresh: refresh,
215
          child: ListView(
216
            physics: const AlwaysScrollableScrollPhysics(),
217
            children: const <Widget>[
218
              SizedBox(
219
                height: 200.0,
220
                child: Text('X'),
221 222 223
              ),
            ],
          ),
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
        ),
      ),
    );

    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(
239 240
      MaterialApp(
        home: RefreshIndicator(
241
          onRefresh: holdRefresh, // this one never returns
242
          child: ListView(
243
            physics: const AlwaysScrollableScrollPhysics(),
244
            children: const <Widget>[
245
              SizedBox(
246
                height: 200.0,
247
                child: Text('X'),
248 249 250
              ),
            ],
          ),
251 252 253 254 255 256 257
        ),
      ),
    );

    bool completed = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
258
      .then<void>((void value) { completed = true; });
259 260 261 262 263 264 265 266 267 268 269
    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()
270
      .then<void>((void value) { completed = true; });
271 272 273 274 275 276 277 278 279 280 281
    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(
282 283
      MaterialApp(
        home: RefreshIndicator(
284
          onRefresh: refresh,
285
          child: ListView(
286
            physics: const AlwaysScrollableScrollPhysics(),
287
            children: const <Widget>[
288
              SizedBox(
289
                height: 200.0,
290
                child: Text('X'),
291 292 293
              ),
            ],
          ),
294 295 296 297 298 299 300
        ),
      ),
    );

    bool completed = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
301
      .then<void>((void value) { completed = true; });
302 303 304 305 306 307 308 309 310 311 312
    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()
313
      .then<void>((void value) { completed = true; });
314 315 316 317 318 319 320 321 322 323 324 325
    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(
326 327
      MaterialApp(
        home: RefreshIndicator(
328
          onRefresh: refresh,
329
          child: ListView(
330
            physics: const AlwaysScrollableScrollPhysics(),
331
            children: const <Widget>[
332
              SizedBox(
333
                height: 200.0,
334
                child: Text('X'),
335 336 337
              ),
            ],
          ),
338 339 340 341 342 343 344
        ),
      ),
    );

    bool completed1 = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
345
      .then<void>((void value) { completed1 = true; });
346 347 348
    bool completed2 = false;
    tester.state<RefreshIndicatorState>(find.byType(RefreshIndicator))
      .show()
349
      .then<void>((void value) { completed2 = true; });
350 351 352 353 354 355 356 357 358 359
    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);
  });
360 361 362 363

  testWidgets('RefreshIndicator - onRefresh asserts', (WidgetTester tester) async {
    refreshCalled = false;
    await tester.pumpWidget(
364 365
      MaterialApp(
        home: RefreshIndicator(
366 367
          onRefresh: () {
            refreshCalled = true;
368
            return null; // Missing a returned Future value here, should cause framework to throw.
369
          },
370
          child: ListView(
371
            physics: const AlwaysScrollableScrollPhysics(),
372
            children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map<Widget>((String item) {
373
              return SizedBox(
374
                height: 200.0,
375
                child: Text(item),
376 377 378 379 380 381 382 383 384 385 386 387 388
              );
            }).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);
  });
389 390 391 392 393

  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;
394
    final ScrollController controller = ScrollController();
395 396

    await tester.pumpWidget(
397 398
      MaterialApp(
        home: RefreshIndicator(
399
          onRefresh: refresh,
400
          child: ListView(
401 402
            controller: controller,
            physics: const AlwaysScrollableScrollPhysics(),
403
            children: <String>['A', 'B', 'C', 'D', 'E', 'F'].map<Widget>((String item) {
404
              return SizedBox(
405
                height: 200.0,
406
                child: Text(item),
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
              );
            }).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;
  });
426
}