page_view_test.dart 26.7 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4 5
// 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 'package:flutter_test/flutter_test.dart';
6
import 'package:flutter/material.dart';
7
import 'package:flutter/rendering.dart';
Adam Barth's avatar
Adam Barth committed
8
import 'package:flutter/widgets.dart';
9
import 'package:flutter/gestures.dart' show DragStartBehavior;
Adam Barth's avatar
Adam Barth committed
10

11
import 'semantics_tester.dart';
Adam Barth's avatar
Adam Barth committed
12 13
import 'states.dart';

14
const Duration _frameDuration = Duration(milliseconds: 100);
Adam Barth's avatar
Adam Barth committed
15 16 17

void main() {
  testWidgets('PageView control test', (WidgetTester tester) async {
18
    final List<String> log = <String>[];
Adam Barth's avatar
Adam Barth committed
19

20
    await tester.pumpWidget(Directionality(
21
      textDirection: TextDirection.ltr,
22
      child: PageView(
23
        dragStartBehavior: DragStartBehavior.down,
24
        children: kStates.map<Widget>((String state) {
25
          return GestureDetector(
26
            dragStartBehavior: DragStartBehavior.down,
27 28 29
            onTap: () {
              log.add(state);
            },
30
            child: Container(
31 32
              height: 200.0,
              color: const Color(0xFF0000FF),
33
              child: Text(state),
34 35 36 37
            ),
          );
        }).toList(),
      ),
Adam Barth's avatar
Adam Barth committed
38 39 40 41 42 43 44 45
    ));

    await tester.tap(find.text('Alabama'));
    expect(log, equals(<String>['Alabama']));
    log.clear();

    expect(find.text('Alaska'), findsNothing);

46
    await tester.drag(find.byType(PageView), const Offset(-20.0, 0.0));
Adam Barth's avatar
Adam Barth committed
47 48 49 50 51 52
    await tester.pump();

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);

53
    await tester.pumpAndSettle(_frameDuration);
Adam Barth's avatar
Adam Barth committed
54 55 56 57

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsNothing);

58 59
    await tester.drag(find.byType(PageView), const Offset(-401.0, 0.0));
    await tester.pumpAndSettle(_frameDuration);
Adam Barth's avatar
Adam Barth committed
60 61 62 63 64 65 66 67 68

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);

    await tester.tap(find.text('Alaska'));
    expect(log, equals(<String>['Alaska']));
    log.clear();

69
    await tester.fling(find.byType(PageView), const Offset(-200.0, 0.0), 1000.0);
70
    await tester.pumpAndSettle(_frameDuration);
Adam Barth's avatar
Adam Barth committed
71 72 73 74 75 76

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsNothing);
    expect(find.text('Arizona'), findsOneWidget);

    await tester.fling(find.byType(PageView), const Offset(200.0, 0.0), 1000.0);
77
    await tester.pumpAndSettle(_frameDuration);
Adam Barth's avatar
Adam Barth committed
78 79 80 81 82

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);
  });
83

84
  testWidgets('PageView does not squish when overscrolled', (WidgetTester tester) async {
85 86 87 88 89 90
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(platform: TargetPlatform.iOS),
      home: PageView(
        children: List<Widget>.generate(10, (int i) {
          return Container(
            key: ValueKey<int>(i),
91
            color: const Color(0xFF0000FF),
92 93 94 95 96
          );
        }),
      ),
    ));

97 98
    Size sizeOf(int i) => tester.getSize(find.byKey(ValueKey<int>(i)));
    double leftOf(int i) => tester.getTopLeft(find.byKey(ValueKey<int>(i))).dx;
99 100 101 102

    expect(leftOf(0), equals(0.0));
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));

103
    // Going into overscroll.
104
    await tester.drag(find.byType(PageView), const Offset(100.0, 0.0));
105 106
    await tester.pump();

107
    expect(leftOf(0), greaterThan(0.0));
108 109
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));

110
    // Easing overscroll past overscroll limit.
111
    await tester.drag(find.byType(PageView), const Offset(-200.0, 0.0));
112 113
    await tester.pump();

114
    expect(leftOf(0), lessThan(0.0));
115 116
    expect(sizeOf(0), equals(const Size(800.0, 600.0)));
  });
117 118

  testWidgets('PageController control test', (WidgetTester tester) async {
119
    final PageController controller = PageController(initialPage: 4);
120

121
    await tester.pumpWidget(Directionality(
122
      textDirection: TextDirection.ltr,
123 124
      child: Center(
        child: SizedBox(
125 126
          width: 600.0,
          height: 400.0,
127
          child: PageView(
128
            controller: controller,
129
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
130
          ),
131 132 133 134 135 136
        ),
      ),
    ));

    expect(find.text('California'), findsOneWidget);

137 138
    controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
    await tester.pumpAndSettle(const Duration(milliseconds: 100));
139 140 141

    expect(find.text('Colorado'), findsOneWidget);

142
    await tester.pumpWidget(Directionality(
143
      textDirection: TextDirection.ltr,
144 145
      child: Center(
        child: SizedBox(
146 147
          width: 300.0,
          height: 400.0,
148
          child: PageView(
149
            controller: controller,
150
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
151
          ),
152 153 154 155 156 157
        ),
      ),
    ));

    expect(find.text('Colorado'), findsOneWidget);

158 159
    controller.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
    await tester.pumpAndSettle(const Duration(milliseconds: 100));
160 161 162 163 164

    expect(find.text('California'), findsOneWidget);
  });

  testWidgets('PageController page stability', (WidgetTester tester) async {
165
    await tester.pumpWidget(Directionality(
166
      textDirection: TextDirection.ltr,
167 168
      child: Center(
        child: SizedBox(
169 170
          width: 600.0,
          height: 400.0,
171 172
          child: PageView(
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
173
          ),
174
        ),
175 176 177 178 179
      ),
    ));

    expect(find.text('Alabama'), findsOneWidget);

180 181
    await tester.drag(find.byType(PageView), const Offset(-1250.0, 0.0));
    await tester.pumpAndSettle(const Duration(milliseconds: 100));
182 183 184

    expect(find.text('Arizona'), findsOneWidget);

185
    await tester.pumpWidget(Directionality(
186
      textDirection: TextDirection.ltr,
187 188
      child: Center(
        child: SizedBox(
189 190
          width: 250.0,
          height: 100.0,
191 192
          child: PageView(
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
193
          ),
194 195 196 197 198 199
        ),
      ),
    ));

    expect(find.text('Arizona'), findsOneWidget);

200
    await tester.pumpWidget(Directionality(
201
      textDirection: TextDirection.ltr,
202 203
      child: Center(
        child: SizedBox(
204 205
          width: 450.0,
          height: 400.0,
206 207
          child: PageView(
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
208
          ),
209 210 211 212 213 214
        ),
      ),
    ));

    expect(find.text('Arizona'), findsOneWidget);
  });
215

216
  testWidgets('PageController nextPage and previousPage return Futures that resolve', (WidgetTester tester) async {
217 218
    final PageController controller = PageController();
    await tester.pumpWidget(Directionality(
219
        textDirection: TextDirection.ltr,
220
        child: PageView(
221
          controller: controller,
222
          children: kStates.map<Widget>((String state) => Text(state)).toList(),
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
        ),
    ));

    bool nextPageCompleted = false;
    controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease)
        .then((_) => nextPageCompleted = true);

    expect(nextPageCompleted, false);
    await tester.pump(const Duration(milliseconds: 200));
    expect(nextPageCompleted, false);
    await tester.pump(const Duration(milliseconds: 200));
    expect(nextPageCompleted, true);


    bool previousPageCompleted = false;
    controller.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease)
        .then((_) => previousPageCompleted = true);

    expect(previousPageCompleted, false);
    await tester.pump(const Duration(milliseconds: 200));
    expect(previousPageCompleted, false);
    await tester.pump(const Duration(milliseconds: 200));
    expect(previousPageCompleted, true);
  });

248
  testWidgets('PageView in zero-size container', (WidgetTester tester) async {
249
    await tester.pumpWidget(Directionality(
250
      textDirection: TextDirection.ltr,
251 252
      child: Center(
        child: SizedBox(
253 254
          width: 0.0,
          height: 0.0,
255 256
          child: PageView(
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
257
          ),
258 259 260 261
        ),
      ),
    ));

262
    expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
263

264
    await tester.pumpWidget(Directionality(
265
      textDirection: TextDirection.ltr,
266 267
      child: Center(
        child: SizedBox(
268 269
          width: 200.0,
          height: 200.0,
270 271
          child: PageView(
            children: kStates.map<Widget>((String state) => Text(state)).toList(),
272
          ),
273 274 275 276 277
        ),
      ),
    ));

    expect(find.text('Alabama'), findsOneWidget);
278
  });
279 280 281

  testWidgets('Page changes at halfway point', (WidgetTester tester) async {
    final List<int> log = <int>[];
282
    await tester.pumpWidget(Directionality(
283
      textDirection: TextDirection.ltr,
284
      child: PageView(
285
        onPageChanged: log.add,
286
        children: kStates.map<Widget>((String state) => Text(state)).toList(),
287
      ),
288 289 290 291
    ));

    expect(log, isEmpty);

292
    final TestGesture gesture =
293
        await tester.startGesture(const Offset(100.0, 100.0));
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
    // The page view is 800.0 wide, so this move is just short of halfway.
    await gesture.moveBy(const Offset(-380.0, 0.0));

    expect(log, isEmpty);

    // We've crossed the halfway mark.
    await gesture.moveBy(const Offset(-40.0, 0.0));

    expect(log, equals(const <int>[1]));
    log.clear();

    // Moving a bit more should not generate redundant notifications.
    await gesture.moveBy(const Offset(-40.0, 0.0));

    expect(log, isEmpty);

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    await gesture.moveBy(const Offset(-40.0, 0.0));
    await tester.pump();

    expect(log, isEmpty);

    await gesture.up();
322
    await tester.pumpAndSettle();
323 324 325 326 327 328 329

    expect(log, isEmpty);

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
  });

330 331
  testWidgets('Bouncing scroll physics ballistics does not overshoot', (WidgetTester tester) async {
    final List<int> log = <int>[];
332
    final PageController controller = PageController(viewportFraction: 0.9);
333

334
    Widget build(PageController controller, { Size size }) {
335
      final Widget pageView = Directionality(
336
        textDirection: TextDirection.ltr,
337
        child: PageView(
338 339 340
          controller: controller,
          onPageChanged: log.add,
          physics: const BouncingScrollPhysics(),
341
          children: kStates.map<Widget>((String state) => Text(state)).toList(),
342 343 344 345
        ),
      );

      if (size != null) {
346
        return OverflowBox(
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
          child: pageView,
          minWidth: size.width,
          minHeight: size.height,
          maxWidth: size.width,
          maxHeight: size.height,
        );
      } else {
        return pageView;
      }
    }

    await tester.pumpWidget(build(controller));
    expect(log, isEmpty);

    // Fling right to move to a non-existent page at the beginning of the
    // PageView, and confirm that the PageView settles back on the first page.
    await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
    await tester.pumpAndSettle();
    expect(log, isEmpty);

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);

    // Try again with a Cupertino "Plus" device size.
    await tester.pumpWidget(build(controller, size: const Size(414.0, 736.0)));
    expect(log, isEmpty);

    await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
    await tester.pumpAndSettle();
    expect(log, isEmpty);

    expect(find.text('Alabama'), findsOneWidget);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsNothing);
  });

384
  testWidgets('PageView viewportFraction', (WidgetTester tester) async {
385
    PageController controller = PageController(viewportFraction: 7/8);
386 387

    Widget build(PageController controller) {
388
      return Directionality(
389
        textDirection: TextDirection.ltr,
390
        child: PageView.builder(
391 392 393
          controller: controller,
          itemCount: kStates.length,
          itemBuilder: (BuildContext context, int index) {
394
            return Container(
395 396
              height: 200.0,
              color: index % 2 == 0
397 398
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
399
              child: Text(kStates[index]),
400 401 402
            );
          },
        ),
403 404 405 406 407
      );
    }

    await tester.pumpWidget(build(controller));

408 409
    expect(tester.getTopLeft(find.text('Alabama')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Alaska')), const Offset(750.0, 0.0));
410 411 412 413

    controller.jumpToPage(10);
    await tester.pump();

414 415 416
    expect(tester.getTopLeft(find.text('Georgia')), const Offset(-650.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(750.0, 0.0));
417

418
    controller = PageController(viewportFraction: 39/40);
419 420 421

    await tester.pumpWidget(build(controller));

422 423 424
    expect(tester.getTopLeft(find.text('Georgia')), const Offset(-770.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(10.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(790.0, 0.0));
425 426
  });

427 428 429 430
  testWidgets('Page snapping disable and reenable', (WidgetTester tester) async {
    final List<int> log = <int>[];

    Widget build({ bool pageSnapping }) {
431
      return Directionality(
432
        textDirection: TextDirection.ltr,
433
        child: PageView(
434 435 436
          pageSnapping: pageSnapping,
          onPageChanged: log.add,
          children:
437
              kStates.map<Widget>((String state) => Text(state)).toList(),
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
        ),
      );
    }

    await tester.pumpWidget(build(pageSnapping: true));
    expect(log, isEmpty);

    // Drag more than halfway to the next page, to confirm the default behavior.
    TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
    // The page view is 800.0 wide, so this move is just beyond halfway.
    await gesture.moveBy(const Offset(-420.0, 0.0));

    expect(log, equals(const <int>[1]));
    log.clear();

    // Release the gesture, confirm that the page settles on the next.
    await gesture.up();
    await tester.pumpAndSettle();

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);

    // Disable page snapping, and try moving halfway. Confirm it doesn't snap.
    await tester.pumpWidget(build(pageSnapping: false));
    gesture = await tester.startGesture(const Offset(100.0, 100.0));
    // Move just beyond halfway, again.
    await gesture.moveBy(const Offset(-420.0, 0.0));

    // Page notifications still get sent.
    expect(log, equals(const <int>[2]));
    log.clear();

    // Release the gesture, confirm that both pages are visible.
    await gesture.up();
    await tester.pumpAndSettle();

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Alaska'), findsOneWidget);
    expect(find.text('Arizona'), findsOneWidget);
    expect(find.text('Arkansas'), findsNothing);

    // Now re-enable snapping, confirm that we've settled on a page.
    await tester.pumpWidget(build(pageSnapping: true));
    await tester.pumpAndSettle();

    expect(log, isEmpty);

    expect(find.text('Alaska'), findsNothing);
    expect(find.text('Arizona'), findsOneWidget);
    expect(find.text('Arkansas'), findsNothing);
  });

490
  testWidgets('PageView small viewportFraction', (WidgetTester tester) async {
491
    final PageController controller = PageController(viewportFraction: 1/8);
492 493

    Widget build(PageController controller) {
494
      return Directionality(
495
        textDirection: TextDirection.ltr,
496
        child: PageView.builder(
497 498 499
          controller: controller,
          itemCount: kStates.length,
          itemBuilder: (BuildContext context, int index) {
500
            return Container(
501 502
              height: 200.0,
              color: index % 2 == 0
503 504
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
505
              child: Text(kStates[index]),
506 507 508
            );
          },
        ),
509 510 511 512 513
      );
    }

    await tester.pumpWidget(build(controller));

514 515 516 517 518
    expect(tester.getTopLeft(find.text('Alabama')), const Offset(350.0, 0.0));
    expect(tester.getTopLeft(find.text('Alaska')), const Offset(450.0, 0.0));
    expect(tester.getTopLeft(find.text('Arizona')), const Offset(550.0, 0.0));
    expect(tester.getTopLeft(find.text('Arkansas')), const Offset(650.0, 0.0));
    expect(tester.getTopLeft(find.text('California')), const Offset(750.0, 0.0));
519 520 521 522

    controller.jumpToPage(10);
    await tester.pump();

523 524 525 526 527 528 529 530 531
    expect(tester.getTopLeft(find.text('Connecticut')), const Offset(-50.0, 0.0));
    expect(tester.getTopLeft(find.text('Delaware')), const Offset(50.0, 0.0));
    expect(tester.getTopLeft(find.text('Florida')), const Offset(150.0, 0.0));
    expect(tester.getTopLeft(find.text('Georgia')), const Offset(250.0, 0.0));
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(350.0, 0.0));
    expect(tester.getTopLeft(find.text('Idaho')), const Offset(450.0, 0.0));
    expect(tester.getTopLeft(find.text('Illinois')), const Offset(550.0, 0.0));
    expect(tester.getTopLeft(find.text('Indiana')), const Offset(650.0, 0.0));
    expect(tester.getTopLeft(find.text('Iowa')), const Offset(750.0, 0.0));
532 533 534
  });

  testWidgets('PageView large viewportFraction', (WidgetTester tester) async {
535
    final PageController controller =
536
        PageController(viewportFraction: 5/4);
537 538

    Widget build(PageController controller) {
539
      return Directionality(
540
        textDirection: TextDirection.ltr,
541
        child: PageView.builder(
542 543 544
          controller: controller,
          itemCount: kStates.length,
          itemBuilder: (BuildContext context, int index) {
545
            return Container(
546 547
              height: 200.0,
              color: index % 2 == 0
548 549
                ? const Color(0xFF0000FF)
                : const Color(0xFF00FF00),
550
              child: Text(kStates[index]),
551 552 553
            );
          },
        ),
554 555 556 557 558
      );
    }

    await tester.pumpWidget(build(controller));

559 560
    expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0));
    expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0));
561 562 563 564

    controller.jumpToPage(10);
    await tester.pump();

565
    expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-100.0, 0.0));
566
  });
567

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
  testWidgets(
    'Updating PageView large viewportFraction',
    (WidgetTester tester) async {
      Widget build(PageController controller) {
        return Directionality(
          textDirection: TextDirection.ltr,
          child: PageView.builder(
            controller: controller,
            itemCount: kStates.length,
            itemBuilder: (BuildContext context, int index) {
              return Container(
                height: 200.0,
                color: index % 2 == 0
                  ? const Color(0xFF0000FF)
                  : const Color(0xFF00FF00),
                child: Text(kStates[index]),
              );
            },
          ),
        );
      }

      final PageController oldController = PageController(viewportFraction: 5/4);
      await tester.pumpWidget(build(oldController));

      expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100, 0));
      expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0));

      final PageController newController = PageController(viewportFraction: 4);
      await tester.pumpWidget(build(newController));
      newController.jumpToPage(10);
      await tester.pump();

      expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-(4 - 1) * 800 / 2, 0));
  });

  testWidgets(
    'All visible pages are able to receive touch events',
    (WidgetTester tester) async {
      final PageController controller = PageController(viewportFraction: 1/4, initialPage: 0);
      int tappedIndex;

      Widget build() {
        return Directionality(
          textDirection: TextDirection.ltr,
          child: PageView.builder(
            controller: controller,
            itemCount: 20,
            itemBuilder: (BuildContext context, int index) {
              return GestureDetector(
                onTap: () => tappedIndex = index,
                child: SizedBox.expand(child: Text('$index')),
              );
            },
          ),
        );
      }

      Iterable<int> visiblePages = const <int> [0, 1, 2];
      await tester.pumpWidget(build());

      // The first 3 items should be visible and tappable.
      for (int index in visiblePages) {
        expect(find.text(index.toString()), findsOneWidget);
        // The center of page 2's x-coordinate is 800, so we have to manually
        // offset it a bit to make sure the tap lands within the screen.
        final Offset center = tester.getCenter(find.text('$index')) - const Offset(3, 0);
        await tester.tapAt(center);
        expect(tappedIndex, index);
      }

      controller.jumpToPage(19);
      await tester.pump();
      // The last 3 items should be visible and tappable.
      visiblePages = const <int> [17, 18, 19];
      for (int index in visiblePages) {
        expect(find.text('$index'), findsOneWidget);
        await tester.tap(find.text('$index'));
        expect(tappedIndex, index);
      }
  });

650
  testWidgets('PageView does not report page changed on overscroll', (WidgetTester tester) async {
651
    final PageController controller = PageController(
652 653
      initialPage: kStates.length - 1,
    );
654 655
    int changeIndex = 0;
    Widget build() {
656
      return Directionality(
657
        textDirection: TextDirection.ltr,
658
        child: PageView(
659
          children:
660
              kStates.map<Widget>((String state) => Text(state)).toList(),
661 662 663 664 665
          controller: controller,
          onPageChanged: (int page) {
            changeIndex = page;
          },
        ),
666 667 668 669 670 671 672 673 674 675
      );
    }

    await tester.pumpWidget(build());
    controller.jumpToPage(kStates.length * 2); // try to move beyond max range
    // change index should be zero, shouldn't fire onPageChanged
    expect(changeIndex, 0);
    await tester.pump();
    expect(changeIndex, 0);
  });
676

677
  testWidgets('PageView can restore page', (WidgetTester tester) async {
678
    final PageController controller = PageController();
679 680 681 682 683 684 685 686 687
    try {
      controller.page;
      fail('Accessing page before attaching should fail.');
    } on AssertionError catch (e) {
      expect(
        e.message,
        'PageController.page cannot be accessed before a PageView is built with it.',
      );
    }
688 689
    final PageStorageBucket bucket = PageStorageBucket();
    await tester.pumpWidget(Directionality(
690
      textDirection: TextDirection.ltr,
691
      child: PageStorage(
692
        bucket: bucket,
693
        child: PageView(
694
          key: const PageStorageKey<String>('PageView'),
695
          controller: controller,
696
          children: const <Widget>[
697 698 699
            Placeholder(),
            Placeholder(),
            Placeholder(),
700 701 702
          ],
        ),
      ),
703
    ));
704 705 706 707 708
    expect(controller.page, 0);
    controller.jumpToPage(2);
    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 1);
    expect(controller.page, 2);
    await tester.pumpWidget(
709
      PageStorage(
710
        bucket: bucket,
711
        child: Container(),
712 713
      ),
    );
714 715 716 717 718 719 720 721 722
    try {
      controller.page;
      fail('Accessing page after detaching all PageViews should fail.');
    } on AssertionError catch (e) {
      expect(
        e.message,
        'PageController.page cannot be accessed before a PageView is built with it.',
      );
    }
723
    await tester.pumpWidget(Directionality(
724
      textDirection: TextDirection.ltr,
725
      child: PageStorage(
726
        bucket: bucket,
727
        child: PageView(
728
          key: const PageStorageKey<String>('PageView'),
729
          controller: controller,
730
          children: const <Widget>[
731 732 733
            Placeholder(),
            Placeholder(),
            Placeholder(),
734 735 736
          ],
        ),
      ),
737
    ));
738
    expect(controller.page, 2);
739

740 741
    final PageController controller2 = PageController(keepPage: false);
    await tester.pumpWidget(Directionality(
742
      textDirection: TextDirection.ltr,
743
      child: PageStorage(
744
        bucket: bucket,
745
        child: PageView(
746
          key: const PageStorageKey<String>('Check it again against your list and see consistency!'),
747
          controller: controller2,
748
          children: const <Widget>[
749 750 751
            Placeholder(),
            Placeholder(),
            Placeholder(),
752 753 754
          ],
        ),
      ),
755
    ));
756
    expect(controller2.page, 0);
757
  });
758 759

  testWidgets('PageView exposes semantics of children', (WidgetTester tester) async {
760
    final SemanticsTester semantics = SemanticsTester(tester);
761

762 763
    final PageController controller = PageController();
    await tester.pumpWidget(Directionality(
764
      textDirection: TextDirection.ltr,
765
      child: PageView(
766
          controller: controller,
767 768 769
          children: List<Widget>.generate(3, (int i) {
            return Semantics(
              child: Text('Page #$i'),
770 771
              container: true,
            );
772
          }),
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
        ),
    ));
    expect(controller.page, 0);

    expect(semantics, includesNodeWith(label: 'Page #0'));
    expect(semantics, isNot(includesNodeWith(label: 'Page #1')));
    expect(semantics, isNot(includesNodeWith(label: 'Page #2')));

    controller.jumpToPage(1);
    await tester.pumpAndSettle();

    expect(semantics, isNot(includesNodeWith(label: 'Page #0')));
    expect(semantics, includesNodeWith(label: 'Page #1'));
    expect(semantics, isNot(includesNodeWith(label: 'Page #2')));

    controller.jumpToPage(2);
    await tester.pumpAndSettle();

    expect(semantics, isNot(includesNodeWith(label: 'Page #0')));
    expect(semantics, isNot(includesNodeWith(label: 'Page #1')));
    expect(semantics, includesNodeWith(label: 'Page #2'));

    semantics.dispose();
  });
797 798

  testWidgets('PageMetrics', (WidgetTester tester) async {
799
    final PageMetrics page = PageMetrics(
800 801 802 803 804 805 806 807 808 809 810 811 812
      minScrollExtent: 100.0,
      maxScrollExtent: 200.0,
      pixels: 150.0,
      viewportDimension: 25.0,
      axisDirection: AxisDirection.right,
      viewportFraction: 1.0,
    );
    expect(page.page, 6);
    final PageMetrics page2 = page.copyWith(
      pixels: page.pixels - 100.0,
    );
    expect(page2.page, 4.0);
  });
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832

  testWidgets('Page controller can handle rounding issue', (WidgetTester tester) async {
    final PageController pageController = PageController();

    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: PageView(
        controller: pageController,
        children: List<Widget>.generate(3, (int i) {
          return Semantics(
            child: Text('Page #$i'),
            container: true,
          );
        }),
      ),
    ));
    // Simulate precision error.
    pageController.position.jumpTo(799.99999999999);
    expect(pageController.page, 1);
  });
Adam Barth's avatar
Adam Barth committed
833
}