sliver_semantics_test.dart 38.8 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 6
import 'dart:ui';

7
import 'package:flutter/material.dart';
8 9
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
10 11 12 13

import 'semantics_tester.dart';

void main() {
14 15 16 17 18 19 20 21 22 23
  group('Sliver Semantics', () {
    setUp(() {
      debugResetSemanticsIdCounter();
    });

    _tests();
  });
}

void _tests() {
24
  testWidgets('excludeFromScrollable works correctly', (WidgetTester tester) async {
25
    final SemanticsTester semantics = SemanticsTester(tester);
26 27 28

    const double appBarExpandedHeight = 200.0;

29 30
    final ScrollController scrollController = ScrollController();
    final List<Widget> listChildren = List<Widget>.generate(30, (int i) {
31
      return SizedBox(
32
        height: appBarExpandedHeight,
33
        child: Text('Item $i'),
34 35
      );
    });
Ian Hickson's avatar
Ian Hickson committed
36
    await tester.pumpWidget(
37
      Semantics(
Ian Hickson's avatar
Ian Hickson committed
38
        textDirection: TextDirection.ltr,
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
        child: Localizations(
          locale: const Locale('en', 'us'),
          delegates: const <LocalizationsDelegate<dynamic>>[
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: MediaQuery(
              data: const MediaQueryData(),
              child: CustomScrollView(
                controller: scrollController,
                slivers: <Widget>[
                  const SliverAppBar(
                    pinned: true,
                    expandedHeight: appBarExpandedHeight,
                    title: Text('Semantics Test with Slivers'),
                  ),
57
                  SliverList(
58 59 60 61
                    delegate: SliverChildListDelegate(listChildren),
                  ),
                ],
              ),
62
            ),
Ian Hickson's avatar
Ian Hickson committed
63
          ),
64
        ),
65
      ),
Ian Hickson's avatar
Ian Hickson committed
66
    );
67 68 69

    // AppBar is child of node with semantic scroll actions.
    expect(semantics, hasSemantics(
70
      TestSemantics.root(
Ian Hickson's avatar
Ian Hickson committed
71
        children: <TestSemantics>[
72
          TestSemantics(
73
            id: 1,
74
            textDirection: TextDirection.ltr,
Ian Hickson's avatar
Ian Hickson committed
75
            children: <TestSemantics>[
76
              TestSemantics(
77
                id: 2,
Ian Hickson's avatar
Ian Hickson committed
78
                children: <TestSemantics>[
79 80 81 82 83 84 85 86 87 88 89 90 91 92
                  TestSemantics(
                    id: 7,
                    children: <TestSemantics>[
                      TestSemantics(
                        id: 8,
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
                        label: 'Semantics Test with Slivers',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
93
                  TestSemantics(
94
                    id: 9,
95 96 97
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
98 99
                    actions: <SemanticsAction>[SemanticsAction.scrollUp],
                    children: <TestSemantics>[
100
                      TestSemantics(
101 102 103 104
                        id: 3,
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
105
                      TestSemantics(
106 107 108 109
                        id: 4,
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
110
                      TestSemantics(
111 112 113 114 115
                        id: 5,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
116
                      TestSemantics(
117 118 119 120 121 122
                        id: 6,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
Ian Hickson's avatar
Ian Hickson committed
123 124 125 126
                  ),
                ],
              ),
            ],
127
          ),
Ian Hickson's avatar
Ian Hickson committed
128 129 130 131
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
132 133 134 135 136 137 138 139
    ));

    // Scroll down far enough to reach the pinned state of the app bar.
    scrollController.jumpTo(appBarExpandedHeight);
    await tester.pump();

    // App bar is NOT a child of node with semantic scroll actions.
    expect(semantics, hasSemantics(
140
      TestSemantics.root(
141
        children: <TestSemantics>[
142
          TestSemantics(
143
            id: 1,
144
            textDirection: TextDirection.ltr,
145
            children: <TestSemantics>[
146
              TestSemantics(
147
                id: 2,
148
                children: <TestSemantics>[
149
                  TestSemantics(
150
                    id: 7,
151 152
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
153
                      TestSemantics(
154
                        id: 8,
155 156 157 158
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
159 160 161 162 163
                        label: 'Semantics Test with Slivers',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
164
                  TestSemantics(
165 166 167 168 169
                    id: 9,
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
170
                    flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
171
                    children: <TestSemantics>[
172
                      TestSemantics(
173 174 175 176
                        id: 3,
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
177
                      TestSemantics(
178 179 180 181
                        id: 4,
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
182
                      TestSemantics(
183 184 185 186
                        id: 5,
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
187
                      TestSemantics(
188 189 190 191 192
                        id: 6,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
193
                      TestSemantics(
194 195 196 197 198 199
                        id: 10,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
200 201 202 203
                  ),
                ],
              ),
            ],
204
          ),
205 206 207 208 209 210 211 212 213 214 215 216
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    // Scroll halfway back to the top, app bar is no longer in pinned state.
    scrollController.jumpTo(appBarExpandedHeight / 2);
    await tester.pump();

    // AppBar is child of node with semantic scroll actions.
    expect(semantics, hasSemantics(
217
      TestSemantics.root(
218
        children: <TestSemantics>[
219
          TestSemantics(
220
            id: 1,
221
            textDirection: TextDirection.ltr,
222
            children: <TestSemantics>[
223
              TestSemantics(
224
                id: 2,
225
                children: <TestSemantics>[
226 227 228 229 230 231 232 233 234 235 236 237 238 239
                  TestSemantics(
                    id: 7,
                    children: <TestSemantics>[
                      TestSemantics(
                        id: 8,
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
                        label: 'Semantics Test with Slivers',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
240
                  TestSemantics(
241
                    id: 9,
242 243 244
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
245 246 247 248 249
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
                    children: <TestSemantics>[
250
                      TestSemantics(
251 252 253 254
                        id: 3,
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
255
                      TestSemantics(
256 257 258 259
                        id: 4,
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
260
                      TestSemantics(
261 262 263 264
                        id: 5,
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
265
                      TestSemantics(
266 267 268 269 270 271
                        id: 6,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
272
                  ),
273 274 275
                ],
              ),
            ],
276
          ),
277 278 279 280 281 282 283 284 285
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    semantics.dispose();
  });

286
  testWidgets('Offscreen sliver are hidden in semantics tree', (WidgetTester tester) async {
287
    final SemanticsTester semantics = SemanticsTester(tester);
288 289 290

    const double containerHeight = 200.0;

291
    final ScrollController scrollController = ScrollController(
292 293
      initialScrollOffset: containerHeight * 1.5,
    );
294 295
    final List<Widget> slivers = List<Widget>.generate(30, (int i) {
      return SliverToBoxAdapter(
296
        child: SizedBox(
297
          height: containerHeight,
298
          child: Text('Item $i', textDirection: TextDirection.ltr),
299 300 301 302
        ),
      );
    });
    await tester.pumpWidget(
303
      Semantics(
304
        textDirection: TextDirection.ltr,
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
        child: Localizations(
          locale: const Locale('en', 'us'),
          delegates: const <LocalizationsDelegate<dynamic>>[
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: Center(
              child: SizedBox(
                height: containerHeight,
                child: CustomScrollView(
                  controller: scrollController,
                  slivers: slivers,
                ),
320
              ),
321
            ),
322 323 324 325 326 327
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(
328
      TestSemantics.root(
329
        children: <TestSemantics>[
330
          TestSemantics(
331
            textDirection: TextDirection.ltr,
332
            children: <TestSemantics>[
333
              TestSemantics(
334
                children: <TestSemantics>[
335
                  TestSemantics(
336 337 338
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
339 340 341 342 343
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
                    children: <TestSemantics>[
344
                      TestSemantics(
345 346 347 348
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
349
                      TestSemantics(
350 351 352
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
353
                      TestSemantics(
354 355 356
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
357
                      TestSemantics(
358 359 360 361 362
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
363
                  ),
364 365 366
                ],
              ),
            ],
367
          ),
368 369 370 371
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
372
      ignoreId: true,
373 374 375 376 377 378
    ));

    semantics.dispose();
  });

  testWidgets('SemanticsNodes of Slivers are in paint order', (WidgetTester tester) async {
379
    final SemanticsTester semantics = SemanticsTester(tester);
380

381 382
    final List<Widget> slivers = List<Widget>.generate(5, (int i) {
      return SliverToBoxAdapter(
383
        child: SizedBox(
384
          height: 20.0,
385
          child: Text('Item $i'),
386 387 388 389
        ),
      );
    });
    await tester.pumpWidget(
390
      Semantics(
391
        textDirection: TextDirection.ltr,
392 393 394 395 396 397 398 399 400 401 402
        child: Localizations(
          locale: const Locale('en', 'us'),
          delegates: const <LocalizationsDelegate<dynamic>>[
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: CustomScrollView(
              slivers: slivers,
            ),
403
          ),
404
        ),
405 406
      ),
    );
407

408
    expect(semantics, hasSemantics(
409
      TestSemantics.root(
410
        children: <TestSemantics>[
411
          TestSemantics(
412
            textDirection: TextDirection.ltr,
413
            children: <TestSemantics>[
414
              TestSemantics(
415
                children: <TestSemantics>[
416
                  TestSemantics(
417 418 419
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
420
                    children: <TestSemantics>[
421
                      TestSemantics(
422 423 424
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
425
                      TestSemantics(
426 427 428
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
429
                      TestSemantics(
430 431 432
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
433
                      TestSemantics(
434 435 436
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
437
                      TestSemantics(
438 439 440 441
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
442
                  ),
443 444 445
                ],
              ),
            ],
446
          ),
447 448 449 450
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
451 452
      ignoreId: true,
      childOrder: DebugSemanticsDumpOrder.inverseHitTest,
453
    ));
454 455

    semantics.dispose();
456
  });
457 458

  testWidgets('SemanticsNodes of a sliver fully covered by another overlapping sliver are excluded', (WidgetTester tester) async {
459
    final SemanticsTester semantics = SemanticsTester(tester);
460

461
    final List<Widget> listChildren = List<Widget>.generate(10, (int i) {
462
      return SizedBox(
463
        height: 200.0,
464
        child: Text('Item $i', textDirection: TextDirection.ltr),
465 466
      );
    });
467 468
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    await tester.pumpWidget(Semantics(
469
      textDirection: TextDirection.ltr,
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
      child: Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: const MediaQueryData(),
            child: CustomScrollView(
              slivers: <Widget>[
                const SliverAppBar(
                  pinned: true,
                  expandedHeight: 100.0,
                  title: Text('AppBar'),
                ),
                SliverList(
                  delegate: SliverChildListDelegate(listChildren),
                ),
              ],
              controller: controller,
            ),
493
          ),
494 495
        ),
      ),
496
    ));
497 498

    expect(semantics, hasSemantics(
499
      TestSemantics.root(
500
        children: <TestSemantics>[
501
          TestSemantics(
502
            textDirection: TextDirection.ltr,
503
            children: <TestSemantics>[
504
              TestSemantics(
505
                children: <TestSemantics>[
506
                  TestSemantics(
507 508
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
509
                      TestSemantics(
510 511 512 513
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
514 515 516 517 518
                        label: 'AppBar',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
519
                  TestSemantics(
520 521 522 523
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
524
                    flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
525
                    children: <TestSemantics>[
526
                      TestSemantics(
527 528 529 530
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
531
                      TestSemantics(
532 533 534
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
535
                      TestSemantics(
536 537 538
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
539
                      TestSemantics(
540 541 542
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
543
                      TestSemantics(
544 545 546 547
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
548
                      TestSemantics(
549 550 551 552 553
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 5',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
554 555
                  ),
                ],
556 557
              ),
            ],
558
          ),
559 560
        ],
      ),
561
      ignoreTransform: true,
562 563
      ignoreId: true,
      ignoreRect: true,
564 565 566 567 568
    ));

    semantics.dispose();
  });

569
  testWidgets('Slivers fully covered by another overlapping sliver are hidden', (WidgetTester tester) async {
570
    final SemanticsTester semantics = SemanticsTester(tester);
571

572 573 574
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = List<Widget>.generate(10, (int i) {
      return SliverToBoxAdapter(
575
        child: SizedBox(
576
          height: 200.0,
577
          child: Text('Item $i', textDirection: TextDirection.ltr),
578 579 580
        ),
      );
    });
581
    await tester.pumpWidget(Semantics(
582
      textDirection: TextDirection.ltr,
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
      child: Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: const MediaQueryData(),
            child: CustomScrollView(
              controller: controller,
              slivers: <Widget>[
                const SliverAppBar(
                  pinned: true,
                  expandedHeight: 100.0,
                  title: Text('AppBar'),
                ),
601 602
                ...slivers,
              ],
603
            ),
604
          ),
605 606
        ),
      ),
607
    ));
608 609

    expect(semantics, hasSemantics(
610
      TestSemantics.root(
611
        children: <TestSemantics>[
612
          TestSemantics(
613
            textDirection: TextDirection.ltr,
614
            children: <TestSemantics>[
615
              TestSemantics(
616
                children: <TestSemantics>[
617
                  TestSemantics(
618 619
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
620
                      TestSemantics(
621 622 623 624
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
625 626 627 628 629
                        label: 'AppBar',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
630
                  TestSemantics(
631 632 633 634
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
635
                    flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
636
                    children: <TestSemantics>[
637
                      TestSemantics(
638 639 640 641
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
642
                      TestSemantics(
643 644 645
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
646
                      TestSemantics(
647 648 649
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
650
                      TestSemantics(
651 652 653
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
654
                      TestSemantics(
655 656 657 658
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
659
                      TestSemantics(
660 661 662 663 664
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 5',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
665 666
                  ),
                ],
667 668
              ),
            ],
669
          ),
670 671
        ],
      ),
672
      ignoreTransform: true,
673 674
      ignoreRect: true,
      ignoreId: true,
675 676 677 678 679 680
    ));

    semantics.dispose();
  });

  testWidgets('SemanticsNodes of a sliver fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async {
681
    final SemanticsTester semantics = SemanticsTester(tester);
682

683
    final List<Widget> listChildren = List<Widget>.generate(10, (int i) {
684
      return SizedBox(
685
        height: 200.0,
686
        child: Text('Item $i', textDirection: TextDirection.ltr),
687 688
      );
    });
689 690
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    await tester.pumpWidget(Semantics(
691
      textDirection: TextDirection.ltr,
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
      child: Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: const MediaQueryData(),
            child: CustomScrollView(
              reverse: true, // This is the important setting for this test.
              slivers: <Widget>[
                const SliverAppBar(
                  pinned: true,
                  expandedHeight: 100.0,
                  title: Text('AppBar'),
                ),
                SliverList(
                  delegate: SliverChildListDelegate(listChildren),
                ),
              ],
              controller: controller,
            ),
716
          ),
717 718
        ),
      ),
719
    ));
720 721

    expect(semantics, hasSemantics(
722
      TestSemantics.root(
723
        children: <TestSemantics>[
724
          TestSemantics(
725
            textDirection: TextDirection.ltr,
726
            children: <TestSemantics>[
727
              TestSemantics(
728
                children: <TestSemantics>[
729
                  TestSemantics(
730 731 732
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
733 734 735 736 737
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
                    children: <TestSemantics>[
738
                      TestSemantics(
739 740 741 742
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 5',
                        textDirection: TextDirection.ltr,
                      ),
743
                      TestSemantics(
744 745 746 747
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
748
                      TestSemantics(
749 750 751
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
752
                      TestSemantics(
753 754 755
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
756
                      TestSemantics(
757 758 759
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
760
                      TestSemantics(
761 762 763 764 765 766
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
767
                  TestSemantics(
768 769
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
770
                      TestSemantics(
771 772 773 774
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
775 776 777 778
                        label: 'AppBar',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
779 780
                  ),
                ],
781 782
              ),
            ],
783
          ),
784 785
        ],
      ),
786
      ignoreTransform: true,
787 788
      ignoreId: true,
      ignoreRect: true,
789 790 791 792 793
    ));

    semantics.dispose();
  });

794
  testWidgets('Slivers fully covered by another overlapping sliver are hidden (reverse)', (WidgetTester tester) async {
795
    final SemanticsTester semantics = SemanticsTester(tester);
796

797 798 799
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = List<Widget>.generate(10, (int i) {
      return SliverToBoxAdapter(
800
        child: SizedBox(
801
          height: 200.0,
802
          child: Text('Item $i', textDirection: TextDirection.ltr),
803 804 805
        ),
      );
    });
806
    await tester.pumpWidget(Semantics(
807
      textDirection: TextDirection.ltr,
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
      child: Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: const MediaQueryData(),
            child: CustomScrollView(
              reverse: true, // This is the important setting for this test.
              controller: controller,
              slivers: <Widget>[
                const SliverAppBar(
                  pinned: true,
                  expandedHeight: 100.0,
                  title: Text('AppBar'),
                ),
827 828
                ...slivers,
              ],
829
            ),
830
          ),
831 832
        ),
      ),
833
    ));
834 835

    expect(semantics, hasSemantics(
836
      TestSemantics.root(
837
        children: <TestSemantics>[
838
          TestSemantics(
839
            textDirection: TextDirection.ltr,
840
            children: <TestSemantics>[
841
              TestSemantics(
842
                children: <TestSemantics>[
843
                  TestSemantics(
844 845 846 847 848 849 850
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
851
                    children: <TestSemantics>[
852
                      TestSemantics(
853 854 855 856
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 5',
                        textDirection: TextDirection.ltr,
                      ),
857
                      TestSemantics(
858 859 860 861
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
862
                      TestSemantics(
863 864 865
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
866
                      TestSemantics(
867 868 869
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
870
                      TestSemantics(
871 872 873
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
874
                      TestSemantics(
875 876 877 878 879 880
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
881
                  TestSemantics(
882 883
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
884
                      TestSemantics(
885 886 887 888
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
889 890 891 892
                        label: 'AppBar',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
893 894
                  ),
                ],
895 896
              ),
            ],
897
          ),
898 899
        ],
      ),
900
      ignoreTransform: true,
901 902
      ignoreId: true,
      ignoreRect: true,
903 904 905 906 907
    ));

    semantics.dispose();
  });

908
  testWidgets('Slivers fully covered by another overlapping sliver are hidden (with center sliver)', (WidgetTester tester) async {
909
    final SemanticsTester semantics = SemanticsTester(tester);
910

911 912 913
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    final GlobalKey forwardAppBarKey = GlobalKey(debugLabel: 'forward app bar');
    final List<Widget> forwardChildren = List<Widget>.generate(10, (int i) {
914
      return SizedBox(
915
        height: 200.0,
916
        child: Text('Forward Item $i', textDirection: TextDirection.ltr),
917 918
      );
    });
919
    final List<Widget> backwardChildren = List<Widget>.generate(10, (int i) {
920
      return SizedBox(
921
        height: 200.0,
922
        child: Text('Backward Item $i', textDirection: TextDirection.ltr),
923 924
      );
    });
925
    await tester.pumpWidget(Semantics(
926
      textDirection: TextDirection.ltr,
927
      child: Directionality(
928
        textDirection: TextDirection.ltr,
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
        child: Localizations(
          locale: const Locale('en', 'us'),
          delegates: const <LocalizationsDelegate<dynamic>>[
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
          child: MediaQuery(
            data: const MediaQueryData(),
            child: Scrollable(
              controller: controller,
              viewportBuilder: (BuildContext context, ViewportOffset offset) {
                return Viewport(
                  offset: offset,
                  center: forwardAppBarKey,
                  slivers: <Widget>[
                    SliverList(
                      delegate: SliverChildListDelegate(backwardChildren),
946
                    ),
947 948 949 950 951 952
                    const SliverAppBar(
                      pinned: true,
                      expandedHeight: 100.0,
                      flexibleSpace: FlexibleSpaceBar(
                        title: Text('Backward app bar', textDirection: TextDirection.ltr),
                      ),
953
                    ),
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
                    SliverAppBar(
                      pinned: true,
                      key: forwardAppBarKey,
                      expandedHeight: 100.0,
                      flexibleSpace: const FlexibleSpaceBar(
                        title: Text('Forward app bar', textDirection: TextDirection.ltr),
                      ),
                    ),
                    SliverList(
                      delegate: SliverChildListDelegate(forwardChildren),
                    ),
                  ],
                );
              },
            ),
969
          ),
970 971
        ),
      ),
972
    ));
973 974

    // 'Forward Item 0' is covered by app bar.
975
    expect(semantics, hasSemantics(
976
      TestSemantics.root(
977
        children: <TestSemantics>[
978
          TestSemantics(
979 980
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
981
              TestSemantics(
982
                children: <TestSemantics>[
983
                  TestSemantics(
984 985
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
986 987 988 989 990 991 992 993 994 995 996
                      TestSemantics(),
                      TestSemantics(
                        children: <TestSemantics>[
                          TestSemantics(
                            flags: <SemanticsFlag>[
                              SemanticsFlag.namesRoute,
                              SemanticsFlag.isHeader,
                            ],
                            label: 'Forward app bar',
                            textDirection: TextDirection.ltr,
                          ),
997
                        ],
998 999 1000
                      ),
                    ],
                  ),
1001
                  TestSemantics(
1002 1003 1004 1005
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
1006
                    flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
1007
                    children: <TestSemantics>[
1008
                      TestSemantics(
1009 1010 1011 1012
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Forward Item 0',
                        textDirection: TextDirection.ltr,
                      ),
1013
                      TestSemantics(
1014 1015 1016
                        label: 'Forward Item 1',
                        textDirection: TextDirection.ltr,
                      ),
1017
                      TestSemantics(
1018 1019 1020
                        label: 'Forward Item 2',
                        textDirection: TextDirection.ltr,
                      ),
1021
                      TestSemantics(
1022 1023 1024
                        label: 'Forward Item 3',
                        textDirection: TextDirection.ltr,
                      ),
1025
                      TestSemantics(
1026 1027 1028 1029
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Forward Item 4',
                        textDirection: TextDirection.ltr,
                      ),
1030
                      TestSemantics(
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Forward Item 5',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
      ignoreTransform: true,
      ignoreRect: true,
      ignoreId: true,
    ));
1047 1048 1049

    controller.jumpTo(-880.0);
    await tester.pumpAndSettle();
1050 1051 1052

    // 'Backward Item 0' is covered by app bar.
    expect(semantics, hasSemantics(
1053
      TestSemantics.root(
1054
        children: <TestSemantics>[
1055
          TestSemantics(
1056 1057
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
1058
              TestSemantics(
1059
                children: <TestSemantics>[
1060
                  TestSemantics(
1061 1062 1063
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
1064 1065 1066 1067 1068
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
                    children: <TestSemantics>[
1069
                      TestSemantics(
1070 1071 1072 1073
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Backward Item 5',
                        textDirection: TextDirection.ltr,
                      ),
1074
                      TestSemantics(
1075 1076 1077 1078
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Backward Item 4',
                        textDirection: TextDirection.ltr,
                      ),
1079
                      TestSemantics(
1080 1081 1082
                        label: 'Backward Item 3',
                        textDirection: TextDirection.ltr,
                      ),
1083
                      TestSemantics(
1084 1085 1086
                        label: 'Backward Item 2',
                        textDirection: TextDirection.ltr,
                      ),
1087
                      TestSemantics(
1088 1089 1090
                        label: 'Backward Item 1',
                        textDirection: TextDirection.ltr,
                      ),
1091
                      TestSemantics(
1092 1093 1094 1095 1096 1097
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Backward Item 0',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
1098
                  TestSemantics(
1099 1100
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
                      TestSemantics(),
                      TestSemantics(
                        children: <TestSemantics>[
                          TestSemantics(
                            flags: <SemanticsFlag>[
                              SemanticsFlag.namesRoute,
                              SemanticsFlag.isHeader,
                            ],
                            label: 'Backward app bar',
                            textDirection: TextDirection.ltr,
                          ),
1112
                        ],
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
      ), ignoreTransform: true, ignoreRect: true, ignoreId: true,
    ));
1123 1124 1125 1126

    semantics.dispose();
  });

1127
}