grid_view_test.dart 28.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/gestures.dart' show DragStartBehavior;
6 7 8
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
9
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
10

11
import '../rendering/rendering_tester.dart' show TestClipPaintingContext;
12 13 14
import 'states.dart';

void main() {
15
  // Regression test for https://github.com/flutter/flutter/issues/100451
16
  testWidgetsWithLeakTracking('GridView.builder respects findChildIndexCallback', (WidgetTester tester) async {
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    bool finderCalled = false;
    int itemCount = 7;
    late StateSetter stateSetter;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            stateSetter = setState;
            return GridView.builder(
              itemCount: itemCount,
              itemBuilder: (BuildContext _, int index) => Container(
                key: Key('$index'),
                height: 2000.0,
              ),
              findChildIndexCallback: (Key key) {
                finderCalled = true;
                return null;
              },
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 4,
              ),
            );
          },
        ),
      )
    );
    expect(finderCalled, false);

    // Trigger update.
    stateSetter(() => itemCount = 77);
    await tester.pump();

    expect(finderCalled, true);
  });

54
  testWidgetsWithLeakTracking('Empty GridView', (WidgetTester tester) async {
55
    await tester.pumpWidget(
56
      Directionality(
57
        textDirection: TextDirection.ltr,
58
        child: GridView.count(
59
          dragStartBehavior: DragStartBehavior.down,
60 61 62 63
          crossAxisCount: 4,
        ),
      ),
    );
64 65
  });

66
  testWidgetsWithLeakTracking('GridView.count control test', (WidgetTester tester) async {
67
    final List<String> log = <String>[];
68

69
    await tester.pumpWidget(
70
      Directionality(
71
        textDirection: TextDirection.ltr,
72
        child: GridView.count(
73
          dragStartBehavior: DragStartBehavior.down,
74
          crossAxisCount: 4,
75
          children: kStates.map<Widget>((String state) {
76
            return GestureDetector(
77
              dragStartBehavior: DragStartBehavior.down,
78 79 80
              onTap: () {
                log.add(state);
              },
81
              child: ColoredBox(
82
                color: const Color(0xFF0000FF),
83
                child: Text(state),
84 85 86 87 88 89
              ),
            );
          }).toList(),
        ),
      ),
    );
90 91 92 93 94 95 96 97 98 99 100 101

    expect(tester.getSize(find.text('Arkansas')), equals(const Size(200.0, 200.0)));

    for (int i = 0; i < 8; ++i) {
      await tester.tap(find.text(kStates[i]));
      expect(log, equals(<String>[kStates[i]]));
      log.clear();
    }

    expect(find.text(kStates[12]), findsNothing);
    expect(find.text('Nevada'), findsNothing);

102
    await tester.drag(find.text('Arkansas'), const Offset(0.0, -200.0));
103 104
    await tester.pump();

105
    for (int i = 0; i < 4; ++i) {
106
      expect(find.text(kStates[i]), findsNothing);
107
    }
108 109 110 111 112 113 114

    for (int i = 4; i < 12; ++i) {
      await tester.tap(find.text(kStates[i]));
      expect(log, equals(<String>[kStates[i]]));
      log.clear();
    }

115
    await tester.drag(find.text('Delaware'), const Offset(0.0, -4000.0));
116 117 118 119 120
    await tester.pump();

    expect(find.text('Alabama'), findsNothing);
    expect(find.text('Pennsylvania'), findsNothing);

121
    expect(tester.getCenter(find.text('Tennessee')), equals(const Offset(300.0, 100.0)));
122 123 124 125 126

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

127
    await tester.drag(find.text('Tennessee'), const Offset(0.0, 200.0));
128 129 130 131 132 133 134 135 136 137 138
    await tester.pump();

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

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

139
  testWidgetsWithLeakTracking('GridView.extent control test', (WidgetTester tester) async {
140
    final List<String> log = <String>[];
141

142
    await tester.pumpWidget(
143
      Directionality(
144
        textDirection: TextDirection.ltr,
145
        child: GridView.extent(
146
          dragStartBehavior: DragStartBehavior.down,
147
          maxCrossAxisExtent: 200.0,
148
          children: kStates.map<Widget>((String state) {
149
            return GestureDetector(
150
              dragStartBehavior: DragStartBehavior.down,
151 152 153
              onTap: () {
                log.add(state);
              },
154
              child: ColoredBox(
155
                color: const Color(0xFF0000FF),
156
                child: Text(state),
157 158 159 160 161 162
              ),
            );
          }).toList(),
        ),
      ),
    );
163 164 165 166 167 168 169 170 171 172 173

    expect(tester.getSize(find.text('Arkansas')), equals(const Size(200.0, 200.0)));

    for (int i = 0; i < 8; ++i) {
      await tester.tap(find.text(kStates[i]));
      expect(log, equals(<String>[kStates[i]]));
      log.clear();
    }

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

174
    await tester.drag(find.text('Arkansas'), const Offset(0.0, -4000.0));
175 176 177 178
    await tester.pump();

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

179
    expect(tester.getCenter(find.text('Tennessee')), equals(const Offset(300.0, 100.0)));
180 181 182 183 184 185

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

186
  testWidgetsWithLeakTracking('GridView large scroll jump', (WidgetTester tester) async {
187
    final List<int> log = <int>[];
188

189
    await tester.pumpWidget(
190
      Directionality(
191
        textDirection: TextDirection.ltr,
192
        child: GridView.extent(
193 194 195
          scrollDirection: Axis.horizontal,
          maxCrossAxisExtent: 200.0,
          childAspectRatio: 0.75,
196 197
          children: List<Widget>.generate(80, (int i) {
            return Builder(
198 199
              builder: (BuildContext context) {
                log.add(i);
200
                return Text('$i');
201
              },
202 203 204
            );
          }),
        ),
205
      ),
206
    );
207 208 209 210 211 212 213

    expect(tester.getSize(find.text('4')), equals(const Size(200.0 / 0.75, 200.0)));

    expect(log, equals(<int>[
      0, 1, 2, // col 0
      3, 4, 5, // col 1
      6, 7, 8, // col 2
214
      9, 10, 11, // col 3 (in cached area)
215 216 217
    ]));
    log.clear();

218 219 220 221 222 223 224
    for (int i = 0; i < 9; i++) {
      expect(find.text('$i'), findsOneWidget);
    }
    for (int i = 9; i < 80; i++) {
      expect(find.text('$i'), findsNothing);
    }

225 226
    final ScrollableState state = tester.state(find.byType(Scrollable));
    final ScrollPosition position = state.position;
227 228 229 230 231 232
    position.jumpTo(3025.0);

    expect(log, isEmpty);
    await tester.pump();

    expect(log, equals(<int>[
233
      30, 31, 32, // col 10 (in cached area)
234 235 236 237
      33, 34, 35, // col 11
      36, 37, 38, // col 12
      39, 40, 41, // col 13
      42, 43, 44, // col 14
238
      45, 46, 47, // col 15 (in cached area)
239 240 241
    ]));
    log.clear();

242 243 244 245 246 247 248 249 250 251
    for (int i = 0; i < 33; i++) {
      expect(find.text('$i'), findsNothing);
    }
    for (int i = 33; i < 45; i++) {
      expect(find.text('$i'), findsOneWidget);
    }
    for (int i = 45; i < 80; i++) {
      expect(find.text('$i'), findsNothing);
    }

252 253 254 255 256 257
    position.jumpTo(975.0);

    expect(log, isEmpty);
    await tester.pump();

    expect(log, equals(<int>[
258
      6, 7, 8, // col2 (in cached area)
259 260 261 262
      9, 10, 11, // col 3
      12, 13, 14, // col 4
      15, 16, 17, // col 5
      18, 19, 20, // col 6
263
      21, 22, 23, // col 7 (in cached area)
264 265
    ]));
    log.clear();
266 267 268 269 270 271 272 273 274 275

    for (int i = 0; i < 9; i++) {
      expect(find.text('$i'), findsNothing);
    }
    for (int i = 9; i < 21; i++) {
      expect(find.text('$i'), findsOneWidget);
    }
    for (int i = 21; i < 80; i++) {
      expect(find.text('$i'), findsNothing);
    }
276 277
  });

278
  testWidgetsWithLeakTracking('GridView - change crossAxisCount', (WidgetTester tester) async {
279
    final List<int> log = <int>[];
280 281

    await tester.pumpWidget(
282
      Directionality(
283
        textDirection: TextDirection.ltr,
284
        child: GridView(
285 286 287
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 4,
          ),
288 289
          children: List<Widget>.generate(40, (int i) {
            return Builder(
290 291
              builder: (BuildContext context) {
                log.add(i);
292
                return Text('$i');
293
              },
294 295
            );
          }),
296 297 298 299 300 301 302 303 304 305
        ),
      ),
    );

    expect(tester.getSize(find.text('4')), equals(const Size(200.0, 200.0)));

    expect(log, equals(<int>[
      0, 1, 2, 3, // row 0
      4, 5, 6, 7, // row 1
      8, 9, 10, 11, // row 2
306 307
      12, 13, 14, 15, // row 3 (in cached area)
      16, 17, 18, 19, // row 4 (in cached area)
308
    ]));
309 310 311 312 313 314
    for (int i = 0; i < 12; i++) {
      expect(find.text('$i'), findsOneWidget);
    }
    for (int i = 12; i < 40; i++) {
      expect(find.text('$i'), findsNothing);
    }
315 316 317
    log.clear();

    await tester.pumpWidget(
318
      Directionality(
319
        textDirection: TextDirection.ltr,
320
        child: GridView(
321 322 323
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
          ),
324 325
          children: List<Widget>.generate(40, (int i) {
            return Builder(
326 327
              builder: (BuildContext context) {
                log.add(i);
328
                return Text('$i');
329
              },
330 331
            );
          }),
332 333 334 335 336 337 338 339
        ),
      ),
    );

    expect(log, equals(<int>[
      0, 1, 2, 3, // row 0
      4, 5, 6, 7, // row 1
      8, 9, 10, 11, // row 2
340 341
      12, 13, 14, 15, // row 3 (in cached area)
      16, 17, 18, 19, // row 4 (in cached area)
342 343 344 345 346 347 348
    ]));
    log.clear();

    expect(tester.getSize(find.text('3')), equals(const Size(400.0, 400.0)));
    expect(find.text('4'), findsNothing);
  });

349
  testWidgetsWithLeakTracking('SliverGridRegularTileLayout - can handle close to zero mainAxisStride', (WidgetTester tester) async {
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    const SliverGridDelegateWithMaxCrossAxisExtent delegate = SliverGridDelegateWithMaxCrossAxisExtent(
      childAspectRatio: 1e300,
      maxCrossAxisExtent: 500.0,
    );
    final SliverGridLayout layout = delegate.getLayout(
      const SliverConstraints(
        axisDirection: AxisDirection.down,
        growthDirection: GrowthDirection.forward,
        userScrollDirection: ScrollDirection.forward,
        scrollOffset: 100.0,
        precedingScrollExtent: 0.0,
        overlap: 0.0,
        remainingPaintExtent: 0.0,
        crossAxisExtent: 500,
        crossAxisDirection: AxisDirection.right,
        viewportMainAxisExtent: 100.0,
        remainingCacheExtent: 0.0,
        cacheOrigin: 0.0,
368
      ),
369 370 371 372
    );
    expect(layout.getMinChildIndexForScrollOffset(1000.0), 0.0);
  });

373
  testWidgetsWithLeakTracking('GridView - change maxChildCrossAxisExtent', (WidgetTester tester) async {
374
    final List<int> log = <int>[];
375 376

    await tester.pumpWidget(
377
      Directionality(
378
        textDirection: TextDirection.ltr,
379
        child: GridView(
380 381 382
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 200.0,
          ),
383 384
          children: List<Widget>.generate(40, (int i) {
            return Builder(
385 386
              builder: (BuildContext context) {
                log.add(i);
387
                return Text('$i');
388
              },
389 390
            );
          }),
391 392 393 394 395 396 397 398 399 400
        ),
      ),
    );

    expect(tester.getSize(find.text('4')), equals(const Size(200.0, 200.0)));

    expect(log, equals(<int>[
      0, 1, 2, 3, // row 0
      4, 5, 6, 7, // row 1
      8, 9, 10, 11, // row 2
401 402
      12, 13, 14, 15, // row 3 (in cached area)
      16, 17, 18, 19, // row 4 (in cached area)
403
    ]));
404 405 406 407 408 409
    for (int i = 0; i < 12; i++) {
      expect(find.text('$i'), findsOneWidget);
    }
    for (int i = 12; i < 40; i++) {
      expect(find.text('$i'), findsNothing);
    }
410 411 412
    log.clear();

    await tester.pumpWidget(
413
      Directionality(
414
        textDirection: TextDirection.ltr,
415
        child: GridView(
416 417 418
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 400.0,
          ),
419 420
          children: List<Widget>.generate(40, (int i) {
            return Builder(
421 422
              builder: (BuildContext context) {
                log.add(i);
423
                return Text('$i');
424
              },
425 426
            );
          }),
427 428 429 430 431 432 433 434
        ),
      ),
    );

    expect(log, equals(<int>[
      0, 1, 2, 3, // row 0
      4, 5, 6, 7, // row 1
      8, 9, 10, 11, // row 2
435 436
      12, 13, 14, 15, // row 3 (in cached area)
      16, 17, 18, 19, // row 4 (in cached area)
437 438 439 440 441 442
    ]));
    log.clear();

    expect(tester.getSize(find.text('3')), equals(const Size(400.0, 400.0)));
    expect(find.text('4'), findsNothing);
  });
443

444
  testWidgetsWithLeakTracking('One-line GridView paints', (WidgetTester tester) async {
445
    const Color green = Color(0xFF00FF00);
Adam Barth's avatar
Adam Barth committed
446

447
    final Container container = Container(
Adam Barth's avatar
Adam Barth committed
448
      decoration: const BoxDecoration(
449
        color: green,
Adam Barth's avatar
Adam Barth committed
450 451 452
      ),
    );

453
    await tester.pumpWidget(
454
      Directionality(
455
        textDirection: TextDirection.ltr,
456 457
        child: Center(
          child: SizedBox(
458
            height: 200.0,
459
            child: GridView.count(
460
              cacheExtent: 0.0,
461 462 463 464
              crossAxisCount: 2,
              children: <Widget>[ container, container, container, container ],
            ),
          ),
Adam Barth's avatar
Adam Barth committed
465 466
        ),
      ),
467
    );
Adam Barth's avatar
Adam Barth committed
468 469 470 471 472

    expect(find.byType(GridView), paints..rect(color: green)..rect(color: green));
    expect(find.byType(GridView), isNot(paints..rect(color: green)..rect(color: green)..rect(color: green)));
  });

473
  testWidgetsWithLeakTracking('GridView in zero context', (WidgetTester tester) async {
474
    await tester.pumpWidget(
475
      Directionality(
476
        textDirection: TextDirection.ltr,
477
        child: Center(
478
          child: SizedBox.shrink(
479
            child: GridView.count(
480
              crossAxisCount: 4,
481
              children: List<Widget>.generate(20, (int i) {
482
                return Text('$i');
483 484 485 486 487 488 489
              }),
            ),
          ),
        ),
      ),
    );

490
    expect(find.text('0'), findsNothing);
491 492 493
    expect(find.text('1'), findsNothing);
  });

494
  testWidgetsWithLeakTracking('GridView in unbounded context', (WidgetTester tester) async {
495
    await tester.pumpWidget(
496
      Directionality(
497
        textDirection: TextDirection.ltr,
498 499
        child: SingleChildScrollView(
          child: GridView.count(
500
            crossAxisCount: 4,
501
            shrinkWrap: true,
502
            children: List<Widget>.generate(20, (int i) {
503
              return Text('$i');
504 505 506 507 508 509 510
            }),
          ),
        ),
      ),
    );

    expect(find.text('0'), findsOneWidget);
511
    expect(find.text('19'), findsOneWidget);
512 513
  });

514
  testWidgetsWithLeakTracking('GridView.builder control test', (WidgetTester tester) async {
515
    await tester.pumpWidget(
516
      Directionality(
517
        textDirection: TextDirection.ltr,
518
        child: GridView.builder(
519 520 521
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 4,
          ),
522
          shrinkWrap: true,
523 524
          itemCount: 20,
          itemBuilder: (BuildContext context, int index) {
525
            return Text('$index');
526
          },
527 528 529
        ),
      ),
    );
530 531 532 533 534
    expect(find.text('0'), findsOneWidget);
    expect(find.text('11'), findsOneWidget);
    expect(find.text('12'), findsNothing);
  });

535
  testWidgetsWithLeakTracking('GridView.builder with undefined itemCount', (WidgetTester tester) async {
536
    await tester.pumpWidget(
537
      Directionality(
538
        textDirection: TextDirection.ltr,
539
        child: GridView.builder(
540 541 542 543 544
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 4,
          ),
          shrinkWrap: true,
          itemBuilder: (BuildContext context, int index) {
545
            return Text('$index');
546 547 548 549 550 551 552 553 554 555 556
          },
        ),
      ),
    );
    expect(find.text('0'), findsOneWidget);
    expect(find.text('11'), findsOneWidget);
    await tester.drag(find.byType(GridView), const Offset(0.0, -300.0));
    await tester.pump(const Duration(milliseconds: 200));
    expect(find.text('13'), findsOneWidget);
  });

557
  testWidgetsWithLeakTracking('GridView cross axis layout', (WidgetTester tester) async {
558
    final Key target = UniqueKey();
559 560

    Widget build(TextDirection textDirection) {
561
      return Directionality(
562
        textDirection: textDirection,
563
        child: GridView.count(
564 565
          crossAxisCount: 4,
          children: <Widget>[
566
            Container(key: target),
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
          ],
        ),
      );
    }

    await tester.pumpWidget(build(TextDirection.ltr));

    expect(tester.getTopLeft(find.byKey(target)), Offset.zero);
    expect(tester.getBottomRight(find.byKey(target)), const Offset(200.0, 200.0));

    await tester.pumpWidget(build(TextDirection.rtl));

    expect(tester.getTopLeft(find.byKey(target)), const Offset(600.0, 0.0));
    expect(tester.getBottomRight(find.byKey(target)), const Offset(800.0, 200.0));
  });
582

583
  testWidgetsWithLeakTracking('GridView crossAxisSpacing', (WidgetTester tester) async {
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
    // Regression test for https://github.com/flutter/flutter/issues/27151.
    final Key target = UniqueKey();

    Widget build(TextDirection textDirection) {
      return Directionality(
        textDirection: textDirection,
        child: GridView.count(
          crossAxisCount: 4,
          crossAxisSpacing: 8.0,
          children: <Widget>[
            Container(key: target),
          ],
        ),
      );
    }

    await tester.pumpWidget(build(TextDirection.ltr));

    expect(tester.getTopLeft(find.byKey(target)), Offset.zero);
    expect(tester.getBottomRight(find.byKey(target)), const Offset(194.0, 194.0));

    await tester.pumpWidget(build(TextDirection.rtl));

    expect(tester.getTopLeft(find.byKey(target)), const Offset(606.0, 0.0));
    expect(tester.getBottomRight(find.byKey(target)), const Offset(800.0, 194.0));
  });
610

611
  testWidgetsWithLeakTracking('GridView does not cache itemBuilder calls', (WidgetTester tester) async {
612 613 614 615 616 617 618 619
    final Map<int, int> counters = <int, int>{};

    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: GridView.builder(
        itemCount: 1000,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
        itemBuilder: (BuildContext context, int index) {
620
          counters[index] = (counters[index] ?? 0) + 1;
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
          return SizedBox(
            key: ValueKey<int>(index),
            width: 200,
            height: 200,
          );
        },
      ),
    ));

    expect(find.byKey(const ValueKey<int>(4)), findsOneWidget);
    expect(counters[4], 1);

    await tester.fling(find.byType(GridView), const Offset(0, -300), 5000);
    await tester.pumpAndSettle();

    expect(find.byKey(const ValueKey<int>(4)), findsNothing);
    expect(counters[4], 1);

    await tester.fling(find.byType(GridView), const Offset(0, 300), 5000);
    await tester.pumpAndSettle();

    expect(find.byKey(const ValueKey<int>(4)), findsOneWidget);
    expect(counters[4], 2);
  });
645

646
  testWidgetsWithLeakTracking('GridView does not report visual overflow unnecessarily', (WidgetTester tester) async {
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
          children: <Widget>[
            Container(height: 200.0),
          ],
        ),
      ),
    );

    // 1st, check that the render object has received the default clip behavior.
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.hardEdge));

    // The context will get Clip.none because there is no actual visual overflow.
    final TestClipPaintingContext context = TestClipPaintingContext();
    renderObject.paint(context, Offset.zero);
    expect(context.clipBehavior, equals(Clip.none));
667 668
    context.dispose();
  });
669 670

  testWidgetsWithLeakTracking('GridView respects clipBehavior', (WidgetTester tester) async {
671 672 673 674 675
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
          children: <Widget>[
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
          ],
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
        ),
      ),
    );

    // 1st, check that the render object has received the default clip behavior.
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.hardEdge));

    // 2nd, check that the painting context has received the default clip behavior.
    final TestClipPaintingContext context = TestClipPaintingContext();
    renderObject.paint(context, Offset.zero);
    expect(context.clipBehavior, equals(Clip.hardEdge));

    // 3rd, pump a new widget to check that the render object can update its clip behavior.
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
          clipBehavior: Clip.antiAlias,
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
          children: <Widget>[
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
            Container(height: 2000.0),
          ],
726 727 728 729 730 731 732 733
        ),
      ),
    );
    expect(renderObject.clipBehavior, equals(Clip.antiAlias));

    // 4th, check that a non-default clip behavior can be sent to the painting context.
    renderObject.paint(context, Offset.zero);
    expect(context.clipBehavior, equals(Clip.antiAlias));
734 735
    context.dispose();
  });
736 737

  testWidgetsWithLeakTracking('GridView.builder respects clipBehavior', (WidgetTester tester) async {
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
          itemCount: 10,
          itemBuilder: (BuildContext _, int __) => Container(height: 2000.0),
          clipBehavior: Clip.antiAlias,
        ),
      ),
    );
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  });

753
  testWidgetsWithLeakTracking('GridView.custom respects clipBehavior', (WidgetTester tester) async {
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView.custom(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
          childrenDelegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) => Container(height: 2000.0),
            childCount: 1,
          ),
          clipBehavior: Clip.antiAlias,
        ),
      ),
    );
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  });

771
  testWidgetsWithLeakTracking('GridView.count respects clipBehavior', (WidgetTester tester) async {
772 773 774 775 776 777
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView.count(
          crossAxisCount: 3,
          clipBehavior: Clip.antiAlias,
778
          children: <Widget>[Container(height: 2000.0)],
779 780 781 782 783 784 785
        ),
      ),
    );
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  });

786
  testWidgetsWithLeakTracking('GridView.extent respects clipBehavior', (WidgetTester tester) async {
787 788 789 790 791 792
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView.extent(
          maxCrossAxisExtent: 1000,
          clipBehavior: Clip.antiAlias,
793
          children: <Widget>[Container(height: 2000.0)],
794 795 796 797 798 799
        ),
      ),
    );
    final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
    expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  });
800

801
  testWidgetsWithLeakTracking('SliverGridDelegateWithFixedCrossAxisCount mainAxisExtent works as expected', (WidgetTester tester) async {
802 803 804 805 806 807 808 809 810 811 812 813 814 815
    const int crossAxisCount = 4;
    const double mainAxisExtent = 100.0;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: crossAxisCount,
            mainAxisExtent: mainAxisExtent,
          ),
          children: List<Widget>.generate(20, (int i) {
            return Builder(
              builder: (BuildContext context) {
816
                return Text('$i');
817
              },
818 819 820 821 822 823 824 825 826
            );
          }),
        ),
      ),
    );

    expect(tester.getSize(find.text('4')), equals(const Size(200.0, mainAxisExtent)));
  });

827
  testWidgetsWithLeakTracking('SliverGridDelegateWithMaxCrossAxisExtent mainAxisExtent works as expected', (WidgetTester tester) async {
828 829 830 831 832 833 834 835 836 837 838 839 840 841
    const double maxCrossAxisExtent = 200.0;
    const double mainAxisExtent = 100.0;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: GridView(
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: maxCrossAxisExtent,
            mainAxisExtent: mainAxisExtent,
          ),
          children: List<Widget>.generate(20, (int i) {
            return Builder(
              builder: (BuildContext context) {
842
                return Text('$i');
843
              },
844 845 846 847 848 849 850 851
            );
          }),
        ),
      ),
    );

    expect(tester.getSize(find.text('4')), equals(const Size(200.0, mainAxisExtent)));
  });
852

853
  testWidgetsWithLeakTracking('SliverGridDelegateWithMaxCrossAxisExtent throws assertion error when maxCrossAxisExtent is 0', (WidgetTester tester) async {
854 855 856 857 858 859 860
    const double maxCrossAxisExtent = 0;

    expect(() => Directionality(
      textDirection: TextDirection.ltr,
      child: GridView.extent(
        maxCrossAxisExtent: maxCrossAxisExtent,
      ),
861
    ), throwsAssertionError);
862 863
  });

864
  testWidgetsWithLeakTracking('SliverGrid sets correct extent for null returning builder delegate', (WidgetTester tester) async {
865 866
    // Regression test for https://github.com/flutter/flutter/issues/130685
    final ScrollController controller = ScrollController();
867
    addTearDown(controller.dispose);
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: GridView.builder(
        controller: controller,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 16,
          mainAxisSpacing: 16,
        ),
        itemBuilder: (BuildContext context, int index) {
          if (index == 12) {
            return null;
          }
          return Container(
            height: 100,
            width: 100,
            color: const Color(0xFFFF8A80),
            alignment: Alignment.center,
            child: Text('item ${index+1}'),
          );
        },
      ),
    ));
    await tester.pumpAndSettle();
892

893 894 895 896 897 898 899 900
    expect(controller.position.maxScrollExtent, double.infinity);
    expect(controller.position.pixels, 0.0);
    await tester.fling(find.byType(GridView), const Offset(0.0, -1300.0), 100.0);
    await tester.pumpAndSettle();
    // The actual extent of the children is 472.0. This should be reflected when
    // the builder returns null (meaning we have reached the end).
    expect(controller.position.maxScrollExtent, 472.0);
    expect(controller.position.pixels, 472.0);
901
  });
902
}