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
// @dart = 2.8

7 8
import 'dart:ui';

9 10 11 12 13 14 15 16
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';

import 'semantics_tester.dart';

void main() {
17 18 19 20 21 22 23 24 25 26
  group('Sliver Semantics', () {
    setUp(() {
      debugResetSemanticsIdCounter();
    });

    _tests();
  });
}

void _tests() {
27
  testWidgets('excludeFromScrollable works correctly', (WidgetTester tester) async {
28
    final SemanticsTester semantics = SemanticsTester(tester);
29 30 31

    const double appBarExpandedHeight = 200.0;

32 33 34
    final ScrollController scrollController = ScrollController();
    final List<Widget> listChildren = List<Widget>.generate(30, (int i) {
      return Container(
35
        height: appBarExpandedHeight,
36
        child: Text('Item $i'),
37 38
      );
    });
Ian Hickson's avatar
Ian Hickson committed
39
    await tester.pumpWidget(
40
      Semantics(
Ian Hickson's avatar
Ian Hickson committed
41
        textDirection: TextDirection.ltr,
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
        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'),
                  ),
60
                  SliverList(
61 62 63 64
                    delegate: SliverChildListDelegate(listChildren),
                  ),
                ],
              ),
65
            ),
Ian Hickson's avatar
Ian Hickson committed
66
          ),
67
        ),
68
      ),
Ian Hickson's avatar
Ian Hickson committed
69
    );
70 71 72

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

    // 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(
143
      TestSemantics.root(
144
        children: <TestSemantics>[
145
          TestSemantics(
146
            id: 1,
147
            textDirection: TextDirection.ltr,
148
            children: <TestSemantics>[
149
              TestSemantics(
150
                id: 2,
151
                children: <TestSemantics>[
152
                  TestSemantics(
153
                    id: 7,
154 155
                    tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
                    children: <TestSemantics>[
156
                      TestSemantics(
157
                        id: 8,
158 159 160 161
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
162 163 164 165 166
                        label: 'Semantics Test with Slivers',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
167
                  TestSemantics(
168 169 170 171 172
                    id: 9,
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
173
                    flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
174
                    children: <TestSemantics>[
175
                      TestSemantics(
176 177 178 179
                        id: 3,
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
180
                      TestSemantics(
181 182 183 184
                        id: 4,
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
185
                      TestSemantics(
186 187 188 189
                        id: 5,
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
190
                      TestSemantics(
191 192 193 194 195
                        id: 6,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
196
                      TestSemantics(
197 198 199 200 201 202
                        id: 10,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 4',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
203 204 205 206
                  ),
                ],
              ),
            ],
207
          ),
208 209 210 211 212 213 214 215 216 217 218 219
        ],
      ),
      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(
220
      TestSemantics.root(
221
        children: <TestSemantics>[
222
          TestSemantics(
223
            id: 1,
224
            textDirection: TextDirection.ltr,
225
            children: <TestSemantics>[
226
              TestSemantics(
227
                id: 2,
228
                children: <TestSemantics>[
229 230 231 232 233 234 235 236 237 238 239 240 241 242
                  TestSemantics(
                    id: 7,
                    children: <TestSemantics>[
                      TestSemantics(
                        id: 8,
                        flags: <SemanticsFlag>[
                          SemanticsFlag.namesRoute,
                          SemanticsFlag.isHeader,
                        ],
                        label: 'Semantics Test with Slivers',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
243
                  TestSemantics(
244
                    id: 9,
245 246 247
                    flags: <SemanticsFlag>[
                      SemanticsFlag.hasImplicitScrolling,
                    ],
248 249 250 251 252
                    actions: <SemanticsAction>[
                      SemanticsAction.scrollUp,
                      SemanticsAction.scrollDown,
                    ],
                    children: <TestSemantics>[
253
                      TestSemantics(
254 255 256 257
                        id: 3,
                        label: 'Item 0',
                        textDirection: TextDirection.ltr,
                      ),
258
                      TestSemantics(
259 260 261 262
                        id: 4,
                        label: 'Item 1',
                        textDirection: TextDirection.ltr,
                      ),
263
                      TestSemantics(
264 265 266 267
                        id: 5,
                        label: 'Item 2',
                        textDirection: TextDirection.ltr,
                      ),
268
                      TestSemantics(
269 270 271 272 273 274
                        id: 6,
                        flags: <SemanticsFlag>[SemanticsFlag.isHidden],
                        label: 'Item 3',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
275
                  ),
276 277 278
                ],
              ),
            ],
279
          ),
280 281 282 283 284 285 286 287 288
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    semantics.dispose();
  });

289
  testWidgets('Offscreen sliver are hidden in semantics tree', (WidgetTester tester) async {
290
    final SemanticsTester semantics = SemanticsTester(tester);
291 292 293

    const double containerHeight = 200.0;

294
    final ScrollController scrollController = ScrollController(
295 296
      initialScrollOffset: containerHeight * 1.5,
    );
297 298 299
    final List<Widget> slivers = List<Widget>.generate(30, (int i) {
      return SliverToBoxAdapter(
        child: Container(
300
          height: containerHeight,
301
          child: Text('Item $i', textDirection: TextDirection.ltr),
302 303 304 305
        ),
      );
    });
    await tester.pumpWidget(
306
      Semantics(
307
        textDirection: TextDirection.ltr,
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
        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,
                ),
323
              ),
324
            ),
325 326 327 328 329 330
          ),
        ),
      ),
    );

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

    semantics.dispose();
  });

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

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

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

    semantics.dispose();
459
  });
460 461

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

464 465
    final List<Widget> listChildren = List<Widget>.generate(10, (int i) {
      return Container(
466
        height: 200.0,
467
        child: Text('Item $i', textDirection: TextDirection.ltr),
468 469
      );
    });
470 471
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    await tester.pumpWidget(Semantics(
472
      textDirection: TextDirection.ltr,
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
      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,
            ),
496
          ),
497 498
        ),
      ),
499
    ));
500 501

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

    semantics.dispose();
  });

572
  testWidgets('Slivers fully covered by another overlapping sliver are hidden', (WidgetTester tester) async {
573
    final SemanticsTester semantics = SemanticsTester(tester);
574

575 576 577 578
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = List<Widget>.generate(10, (int i) {
      return SliverToBoxAdapter(
        child: Container(
579
          height: 200.0,
580
          child: Text('Item $i', textDirection: TextDirection.ltr),
581 582 583
        ),
      );
    });
584
    await tester.pumpWidget(Semantics(
585
      textDirection: TextDirection.ltr,
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
      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'),
                ),
604 605
                ...slivers,
              ],
606
            ),
607
          ),
608 609
        ),
      ),
610
    ));
611 612

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

    semantics.dispose();
  });

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

686 687
    final List<Widget> listChildren = List<Widget>.generate(10, (int i) {
      return Container(
688
        height: 200.0,
689
        child: Text('Item $i', textDirection: TextDirection.ltr),
690 691
      );
    });
692 693
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    await tester.pumpWidget(Semantics(
694
      textDirection: TextDirection.ltr,
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
      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,
            ),
719
          ),
720 721
        ),
      ),
722
    ));
723 724

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

    semantics.dispose();
  });

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

800 801 802 803
    final ScrollController controller = ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = List<Widget>.generate(10, (int i) {
      return SliverToBoxAdapter(
        child: Container(
804
          height: 200.0,
805
          child: Text('Item $i', textDirection: TextDirection.ltr),
806 807 808
        ),
      );
    });
809
    await tester.pumpWidget(Semantics(
810
      textDirection: TextDirection.ltr,
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
      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'),
                ),
830 831
                ...slivers,
              ],
832
            ),
833
          ),
834 835
        ),
      ),
836
    ));
837 838

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

    semantics.dispose();
  });

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

914 915 916 917
    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) {
      return Container(
918
        height: 200.0,
919
        child: Text('Forward Item $i', textDirection: TextDirection.ltr),
920 921
      );
    });
922 923
    final List<Widget> backwardChildren = List<Widget>.generate(10, (int i) {
      return Container(
924
        height: 200.0,
925
        child: Text('Backward Item $i', textDirection: TextDirection.ltr),
926 927
      );
    });
928
    await tester.pumpWidget(Semantics(
929
      textDirection: TextDirection.ltr,
930
      child: Directionality(
931
        textDirection: TextDirection.ltr,
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
        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),
949
                    ),
950 951 952 953 954 955
                    const SliverAppBar(
                      pinned: true,
                      expandedHeight: 100.0,
                      flexibleSpace: FlexibleSpaceBar(
                        title: Text('Backward app bar', textDirection: TextDirection.ltr),
                      ),
956
                    ),
957 958 959 960 961 962 963 964 965 966 967 968 969 970 971
                    SliverAppBar(
                      pinned: true,
                      key: forwardAppBarKey,
                      expandedHeight: 100.0,
                      flexibleSpace: const FlexibleSpaceBar(
                        title: Text('Forward app bar', textDirection: TextDirection.ltr),
                      ),
                    ),
                    SliverList(
                      delegate: SliverChildListDelegate(forwardChildren),
                    ),
                  ],
                );
              },
            ),
972
          ),
973 974
        ),
      ),
975
    ));
976 977

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

    controller.jumpTo(-880.0);
    await tester.pumpAndSettle();
1053 1054 1055

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

    semantics.dispose();
  });

1130
}