sliver_semantics_test.dart 26 KB
Newer Older
1 2 3 4
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui';

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

import 'semantics_tester.dart';

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

    _tests();
  });
}

void _tests() {
26 27 28 29 30 31 32 33 34 35 36 37
  testWidgets('excludeFromScrollable works correctly', (WidgetTester tester) async {
    final SemanticsTester semantics = new SemanticsTester(tester);

    const double appBarExpandedHeight = 200.0;

    final ScrollController scrollController = new ScrollController();
    final List<Widget> listChildren = new List<Widget>.generate(30, (int i) {
      return new Container(
        height: appBarExpandedHeight,
        child: new Text('Item $i'),
      );
    });
Ian Hickson's avatar
Ian Hickson committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new MediaQuery(
          data: const MediaQueryData(),
          child: new CustomScrollView(
            controller: scrollController,
            slivers: <Widget>[
              const SliverAppBar(
                pinned: true,
                expandedHeight: appBarExpandedHeight,
                title: const Text('Semantics Test with Slivers'),
              ),
              new SliverList(
                delegate: new SliverChildListDelegate(listChildren),
              ),
            ],
          ),
56
        ),
57
      ),
Ian Hickson's avatar
Ian Hickson committed
58
    );
59 60 61

    // AppBar is child of node with semantic scroll actions.
    expect(semantics, hasSemantics(
Ian Hickson's avatar
Ian Hickson committed
62 63 64
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
65
            id: 1,
66
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
Ian Hickson's avatar
Ian Hickson committed
67 68
            children: <TestSemantics>[
              new TestSemantics(
69
                id: 6,
70
                actions: <SemanticsAction>[SemanticsAction.scrollUp],
Ian Hickson's avatar
Ian Hickson committed
71 72
                children: <TestSemantics>[
                  new TestSemantics(
73
                    id: 2,
74 75
                    label: r'Item 0',
                    textDirection: TextDirection.ltr,
Ian Hickson's avatar
Ian Hickson committed
76 77
                  ),
                  new TestSemantics(
78
                    id: 3,
79 80
                    label: r'Item 1',
                    textDirection: TextDirection.ltr,
Ian Hickson's avatar
Ian Hickson committed
81 82
                  ),
                  new TestSemantics(
83
                    id: 4,
84
                    flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
85 86
                    label: r'Semantics Test with Slivers',
                    textDirection: TextDirection.ltr,
Ian Hickson's avatar
Ian Hickson committed
87 88 89 90
                  ),
                ],
              ),
            ],
91
          ),
Ian Hickson's avatar
Ian Hickson committed
92 93 94 95
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
96 97 98 99 100 101 102 103 104 105 106
    ));

    // 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(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
107
            id: 1,
108
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
109 110
            children: <TestSemantics>[
              new TestSemantics(
111
                id: 6,
112
                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
113 114
                children: <TestSemantics>[
                  new TestSemantics(
115
                    id: 2,
116 117
                    label: r'Item 0',
                    textDirection: TextDirection.ltr,
118 119
                  ),
                  new TestSemantics(
120
                    id: 3,
121 122
                    label: r'Item 1',
                    textDirection: TextDirection.ltr,
123 124
                  ),
                  new TestSemantics(
125
                    id: 7,
126 127
                    label: r'Item 2',
                    textDirection: TextDirection.ltr,
128 129 130
                  ),
                ],
              ),
131
              new TestSemantics(
132
                id: 4,
133
                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
134 135
                label: r'Semantics Test with Slivers',
                textDirection: TextDirection.ltr,
136
              ),
137
            ],
138
          ),
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
        ],
      ),
      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(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
154
            id: 1,
155
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
156 157
            children: <TestSemantics>[
              new TestSemantics(
158
                id: 6,
159
                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
160
                children: <TestSemantics>[
161
                  new TestSemantics(
162
                    id: 2,
163 164
                    label: r'Item 0',
                    textDirection: TextDirection.ltr,
165 166
                  ),
                  new TestSemantics(
167
                    id: 3,
168 169
                    label: r'Item 1',
                    textDirection: TextDirection.ltr,
170 171
                  ),
                  new TestSemantics(
172
                    id: 7,
173 174
                    label: r'Item 2',
                    textDirection: TextDirection.ltr,
175
                  ),
176
                  new TestSemantics(
177
                    id: 4,
178
                    flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
179 180
                    label: r'Semantics Test with Slivers',
                    textDirection: TextDirection.ltr,
181
                  ),
182 183 184
                ],
              ),
            ],
185
          ),
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    semantics.dispose();
  });

  testWidgets('Offscreen sliver are not included in semantics tree', (WidgetTester tester) async {
    final SemanticsTester semantics = new SemanticsTester(tester);

    const double containerHeight = 200.0;

    final ScrollController scrollController = new ScrollController(
      initialScrollOffset: containerHeight * 1.5,
    );
    final List<Widget> slivers = new List<Widget>.generate(30, (int i) {
      return new SliverToBoxAdapter(
        child: new Container(
          height: containerHeight,
Ian Hickson's avatar
Ian Hickson committed
207
          child: new Text('Item $i', textDirection: TextDirection.ltr),
208 209 210 211
        ),
      );
    });
    await tester.pumpWidget(
212 213 214 215 216 217 218 219 220
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new Center(
          child: new SizedBox(
            height: containerHeight,
            child: new CustomScrollView(
              controller: scrollController,
              slivers: slivers,
            ),
221 222 223 224 225 226 227 228 229
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
230
            id: 1,
231
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
232 233
            children: <TestSemantics>[
              new TestSemantics(
234
                id: 4,
235
                actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
236
                children: <TestSemantics>[
237
                  new TestSemantics(
238
                    id: 2,
239
                    label: 'Item 2',
Ian Hickson's avatar
Ian Hickson committed
240
                    textDirection: TextDirection.ltr,
241 242
                  ),
                  new TestSemantics(
243
                    id: 3,
244
                    label: 'Item 1',
Ian Hickson's avatar
Ian Hickson committed
245
                    textDirection: TextDirection.ltr,
246
                  ),
247 248 249
                ],
              ),
            ],
250
          ),
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    semantics.dispose();
  });

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

    final List<Widget> slivers = new List<Widget>.generate(5, (int i) {
      return new SliverToBoxAdapter(
        child: new Container(
          height: 20.0,
          child: new Text('Item $i'),
        ),
      );
    });
    await tester.pumpWidget(
272 273 274 275 276
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new CustomScrollView(
          slivers: slivers,
        ),
277 278
      ),
    );
279

280 281 282 283
    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
284
            id: 1,
285
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
286 287
            children: <TestSemantics>[
              new TestSemantics(
288
                id: 7,
289 290
                children: <TestSemantics>[
                  new TestSemantics(
291
                    id: 2,
292
                    label: 'Item 4',
Ian Hickson's avatar
Ian Hickson committed
293
                    textDirection: TextDirection.ltr,
294
                  ),
295
                  new TestSemantics(
296
                    id: 3,
297
                    label: 'Item 3',
Ian Hickson's avatar
Ian Hickson committed
298
                    textDirection: TextDirection.ltr,
299 300
                  ),
                  new TestSemantics(
301
                    id: 4,
302
                    label: 'Item 2',
Ian Hickson's avatar
Ian Hickson committed
303
                    textDirection: TextDirection.ltr,
304
                  ),
305
                  new TestSemantics(
306
                    id: 5,
307
                    label: 'Item 1',
Ian Hickson's avatar
Ian Hickson committed
308
                    textDirection: TextDirection.ltr,
309 310
                  ),
                  new TestSemantics(
311
                    id: 6,
312
                    label: 'Item 0',
Ian Hickson's avatar
Ian Hickson committed
313
                    textDirection: TextDirection.ltr,
314
                  ),
315 316 317
                ],
              ),
            ],
318
          ),
319 320 321 322 323
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));
324 325

    semantics.dispose();
326
  });
327 328 329 330 331 332 333

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

    final List<Widget> listChildren = new List<Widget>.generate(10, (int i) {
      return new Container(
        height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
334
        child: new Text('Item $i', textDirection: TextDirection.ltr),
335 336 337
      );
    });
    final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
338 339 340
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new MediaQuery(
341
        data: const MediaQueryData(),
342 343
        child: new CustomScrollView(
          slivers: <Widget>[
344
            const SliverAppBar(
345 346
              pinned: true,
              expandedHeight: 100.0,
347
              title: const Text('AppBar'),
348 349 350 351 352 353 354 355
            ),
            new SliverList(
              delegate: new SliverChildListDelegate(listChildren),
            ),
          ],
          controller: controller,
        ),
      ),
356
    ));
357 358 359 360 361 362 363 364

    // 'Item 0' is covered by app bar.
    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));

    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
365
            id: 1,
366
            rect: TestSemantics.fullScreen,
367
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
368 369
            children: <TestSemantics>[
              new TestSemantics(
370
                id: 7,
371 372 373 374 375
                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
                rect: TestSemantics.fullScreen,
                children: <TestSemantics>[
                  // Item 0 is missing because its covered by the app bar.
                  new TestSemantics(
376
                    id: 2,
377 378 379 380 381 382
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    // Item 1 starts 20.0dp below edge, so there would be room for Item 0.
                    transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
                    label: 'Item 1',
                  ),
                  new TestSemantics(
383
                    id: 3,
384 385 386 387 388
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
                    label: 'Item 2',
                  ),
                  new TestSemantics(
389
                    id: 4,
390 391 392 393 394 395 396
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
                    label: 'Item 3',
                  ),
                ],
              ),
              new TestSemantics(
397
                id: 5,
398
                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
399
                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
400
                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
401 402 403 404 405 406 407 408
                children: <TestSemantics>[
                  new TestSemantics(
                    id: 6,
                    label: 'AppBar',
                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
                    textDirection: TextDirection.ltr,
                  ),
                ],
409 410 411 412 413
              ),
            ],
          )
        ],
      ),
414
      ignoreTransform: true,
415 416 417 418 419 420 421 422 423 424 425 426 427
    ));

    semantics.dispose();
  });

  testWidgets('Slivers fully covered by another overlapping sliver are excluded', (WidgetTester tester) async {
    final SemanticsTester semantics = new SemanticsTester(tester);

    final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = new List<Widget>.generate(10, (int i) {
      return new SliverToBoxAdapter(
        child: new Container(
          height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
428
          child: new Text('Item $i', textDirection: TextDirection.ltr),
429 430 431
        ),
      );
    });
432 433 434
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new MediaQuery(
435
        data: const MediaQueryData(),
436 437 438
        child: new CustomScrollView(
          controller: controller,
          slivers: <Widget>[
439
            const SliverAppBar(
440 441
              pinned: true,
              expandedHeight: 100.0,
442
              title: const Text('AppBar'),
443 444 445 446
            ),
          ]..addAll(slivers),
        ),
      ),
447
    ));
448 449 450 451 452 453 454 455

    // 'Item 0' is covered by app bar.
    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));

    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
456
            id: 1,
457
            rect: TestSemantics.fullScreen,
458
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
459 460
            children: <TestSemantics>[
              new TestSemantics(
461
                id: 7,
462 463 464 465
                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
                rect: TestSemantics.fullScreen,
                children: <TestSemantics>[
                  new TestSemantics(
466
                    id: 2,
467 468 469 470 471
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
                    label: 'Item 3',
                  ),
                  new TestSemantics(
472
                    id: 3,
473 474 475 476 477
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
                    label: 'Item 2',
                  ),
                  new TestSemantics(
478
                    id: 4,
479 480 481 482 483 484 485 486 487
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    // Item 1 starts 20.0dp below edge, so there would be room for Item 0.
                    transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
                    label: 'Item 1',
                  ),
                  // Item 0 is missing because its covered by the app bar.
                ],
              ),
              new TestSemantics(
488
                id: 5,
489
                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
490
                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
491
                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
492 493 494 495 496 497 498
                children: <TestSemantics>[
                  new TestSemantics(
                    id: 6,
                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
                    label: 'AppBar',
                  ),
                ],
499 500 501 502 503
              ),
            ],
          )
        ],
      ),
504
      ignoreTransform: true,
505 506 507 508 509 510 511 512 513 514 515
    ));

    semantics.dispose();
  });

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

    final List<Widget> listChildren = new List<Widget>.generate(10, (int i) {
      return new Container(
        height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
516
        child: new Text('Item $i', textDirection: TextDirection.ltr),
517 518 519
      );
    });
    final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
520 521 522
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new MediaQuery(
523
        data: const MediaQueryData(),
524
        child: new CustomScrollView(
525
          reverse: true, // This is the important setting for this test.
526
          slivers: <Widget>[
527
            const SliverAppBar(
528 529
              pinned: true,
              expandedHeight: 100.0,
530
              title: const Text('AppBar'),
531 532 533 534 535 536 537 538
            ),
            new SliverList(
              delegate: new SliverChildListDelegate(listChildren),
            ),
          ],
          controller: controller,
        ),
      ),
539
    ));
540 541 542 543 544 545 546 547

    // 'Item 0' is covered by app bar.
    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));

    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
548
            id: 1,
549
            rect: TestSemantics.fullScreen,
550
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
551 552
            children: <TestSemantics>[
              new TestSemantics(
553
                id: 7,
554 555 556 557 558
                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
                rect: TestSemantics.fullScreen,
                children: <TestSemantics>[
                  // Item 0 is missing because its covered by the app bar.
                  new TestSemantics(
559
                    id: 2,
560 561 562 563 564
                    // Item 1 ends at 580dp, so there would be 20dp space for Item 0.
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    label: 'Item 1',
                  ),
                  new TestSemantics(
565
                    id: 3,
566 567 568 569
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    label: 'Item 2',
                  ),
                  new TestSemantics(
570
                    id: 4,
571 572 573 574 575 576
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    label: 'Item 3',
                  ),
                ],
              ),
              new TestSemantics(
577
                id: 5,
578
                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
579
                transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
580
                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
581
                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
582 583 584 585 586 587 588 589
                children: <TestSemantics>[
                  new TestSemantics(
                    id: 6,
                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
                    label: 'AppBar',
                    textDirection: TextDirection.ltr,
                  ),
                ],
590 591 592 593 594
              ),
            ],
          )
        ],
      ),
595
      ignoreTransform: true,
596 597 598 599 600 601 602 603 604 605 606 607 608
    ));

    semantics.dispose();
  });

  testWidgets('Slivers fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async {
    final SemanticsTester semantics = new SemanticsTester(tester);

    final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
    final List<Widget> slivers = new List<Widget>.generate(10, (int i) {
      return new SliverToBoxAdapter(
        child: new Container(
          height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
609
          child: new Text('Item $i', textDirection: TextDirection.ltr),
610 611 612
        ),
      );
    });
613 614 615
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new MediaQuery(
616
        data: const MediaQueryData(),
617
        child: new CustomScrollView(
618
          reverse: true, // This is the important setting for this test.
619 620
          controller: controller,
          slivers: <Widget>[
621
            const SliverAppBar(
622 623
              pinned: true,
              expandedHeight: 100.0,
624
              title: const Text('AppBar'),
625 626 627 628
            ),
          ]..addAll(slivers),
        ),
      ),
629
    ));
630 631 632 633 634 635 636 637

    // 'Item 0' is covered by app bar.
    expect(semantics, isNot(includesNodeWith(label: 'Item 0')));

    expect(semantics, hasSemantics(
      new TestSemantics.root(
        children: <TestSemantics>[
          new TestSemantics.rootChild(
638
            id: 1,
639
            rect: TestSemantics.fullScreen,
640
            tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
641 642
            children: <TestSemantics>[
              new TestSemantics(
643
                id: 7,
644 645 646 647
                actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
                rect: TestSemantics.fullScreen,
                children: <TestSemantics>[
                  new TestSemantics(
648
                    id: 2,
649 650 651 652 653
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
                    label: 'Item 3',
                  ),
                  new TestSemantics(
654
                    id: 3,
655 656 657 658 659
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
                    label: 'Item 2',
                  ),
                  new TestSemantics(
660
                    id: 4,
661 662 663 664 665 666 667 668 669
                    rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
                    // Item 1 ends at 580dp, so there would be 20dp space for Item 0.
                    transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
                    label: 'Item 1',
                  ),
                  // Item 0 is missing because its covered by the app bar.
                ],
              ),
              new TestSemantics(
670
                id: 5,
671
                rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
672
                transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
673
                flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
674
                tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
675 676 677 678 679 680 681 682 683
                children: <TestSemantics>[
                  new TestSemantics(
                    id: 6,
                    rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
                    transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
                    label: 'AppBar',
                    textDirection: TextDirection.ltr,
                  ),
                ],
684 685 686 687 688
              ),
            ],
          )
        ],
      ),
689
      ignoreTransform: true,
690 691 692 693 694 695 696 697 698 699 700 701 702
    ));

    semantics.dispose();
  });

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

    final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
    final GlobalKey forwardAppBarKey = new GlobalKey(debugLabel: 'forward app bar');
    final List<Widget> forwardChildren = new List<Widget>.generate(10, (int i) {
      return new Container(
        height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
703
        child: new Text('Forward Item $i', textDirection: TextDirection.ltr),
704 705 706 707 708
      );
    });
    final List<Widget> backwardChildren = new List<Widget>.generate(10, (int i) {
      return new Container(
        height: 200.0,
Ian Hickson's avatar
Ian Hickson committed
709
        child: new Text('Backward Item $i', textDirection: TextDirection.ltr),
710 711
      );
    });
712 713 714
    await tester.pumpWidget(new Directionality(
      textDirection: TextDirection.ltr,
      child: new MediaQuery(
715
        data: const MediaQueryData(),
716 717 718 719 720 721 722 723 724 725
        child: new Scrollable(
          controller: controller,
          viewportBuilder: (BuildContext context, ViewportOffset offset) {
            return new Viewport(
              offset: offset,
              center: forwardAppBarKey,
              slivers: <Widget>[
                new SliverList(
                  delegate: new SliverChildListDelegate(backwardChildren),
                ),
726
                const SliverAppBar(
727 728 729
                  pinned: true,
                  expandedHeight: 100.0,
                  flexibleSpace: const FlexibleSpaceBar(
Ian Hickson's avatar
Ian Hickson committed
730
                    title: const Text('Backward app bar', textDirection: TextDirection.ltr),
731 732 733 734 735 736 737
                  ),
                ),
                new SliverAppBar(
                  pinned: true,
                  key: forwardAppBarKey,
                  expandedHeight: 100.0,
                  flexibleSpace: const FlexibleSpaceBar(
Ian Hickson's avatar
Ian Hickson committed
738
                    title: const Text('Forward app bar', textDirection: TextDirection.ltr),
739 740 741 742 743 744 745 746 747 748
                  ),
                ),
                new SliverList(
                  delegate: new SliverChildListDelegate(forwardChildren),
                ),
              ],
            );
          },
        ),
      ),
749
    ));
750 751 752 753 754 755 756 757 758 759 760 761 762

    // 'Forward Item 0' is covered by app bar.
    expect(semantics, isNot(includesNodeWith(label: 'Forward Item 0')));
    expect(semantics, includesNodeWith(label: 'Forward Item 1'));

    controller.jumpTo(-880.0);
    await tester.pumpAndSettle();
    expect(semantics, isNot(includesNodeWith(label: 'Backward Item 0')));
    expect(semantics, includesNodeWith(label: 'Backward Item 1'));

    semantics.dispose();
  });

763
}