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

Adam Barth's avatar
Adam Barth committed
5
import 'package:flutter_test/flutter_test.dart';
6 7
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
8
import 'package:flutter/gestures.dart';
9

10 11
import '../widgets/semantics_tester.dart';

12
void main() {
13 14 15 16 17 18 19 20 21 22 23 24
  // Pumps and ensures that the BottomSheet animates non-linearly.
  Future<void> _checkNonLinearAnimation(WidgetTester tester) async {
    final Offset firstPosition = tester.getCenter(find.text('BottomSheet'));
    await tester.pump(const Duration(milliseconds: 30));
    final Offset secondPosition = tester.getCenter(find.text('BottomSheet'));
    await tester.pump(const Duration(milliseconds: 30));
    final Offset thirdPosition = tester.getCenter(find.text('BottomSheet'));

    final double dyDelta1 = secondPosition.dy - firstPosition.dy;
    final double dyDelta2 = thirdPosition.dy - secondPosition.dy;

    // If the animation were linear, these two values would be the same.
25
    expect(dyDelta1, isNot(moreOrLessEquals(dyDelta2, epsilon: 0.1)));
26 27
  }

28
  testWidgets('Tapping on a modal BottomSheet should not dismiss it', (WidgetTester tester) async {
29
    late BuildContext savedContext;
30

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    await tester.pumpWidget(
      MaterialApp(
        home: Builder(
          builder: (BuildContext context) {
            savedContext = context;
            return Container();
          },
        ),
      ),
    );

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    bool showBottomSheetThenCalled = false;
    showModalBottomSheet<void>(
      context: savedContext,
      builder: (BuildContext context) => const Text('BottomSheet'),
    ).then<void>((void value) {
      showBottomSheetThenCalled = true;
    });

    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);

    // Tap on the bottom sheet itself, it should not be dismissed
    await tester.tap(find.text('BottomSheet'));
    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);
  });

  testWidgets('Tapping outside a modal BottomSheet should dismiss it by default', (WidgetTester tester) async {
65
    late BuildContext savedContext;
66

67 68
    await tester.pumpWidget(MaterialApp(
      home: Builder(
69 70
        builder: (BuildContext context) {
          savedContext = context;
71
          return Container();
72
        },
73
      ),
74 75
    ));

76
    await tester.pump();
77 78
    expect(find.text('BottomSheet'), findsNothing);

79
    bool showBottomSheetThenCalled = false;
80
    showModalBottomSheet<void>(
81
      context: savedContext,
82
      builder: (BuildContext context) => const Text('BottomSheet'),
83
    ).then<void>((void value) {
84
      showBottomSheetThenCalled = true;
85
    });
86

87
    await tester.pumpAndSettle();
88 89 90
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);

91 92 93 94 95 96 97 98
    // Tap above the bottom sheet to dismiss it.
    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsNothing);
  });

  testWidgets('Tapping outside a modal BottomSheet should dismiss it when isDismissible=true', (WidgetTester tester) async {
99
    late BuildContext savedContext;
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    bool showBottomSheetThenCalled = false;
    showModalBottomSheet<void>(
      context: savedContext,
      builder: (BuildContext context) => const Text('BottomSheet'),
      isDismissible: true,
    ).then<void>((void value) {
      showBottomSheetThenCalled = true;
    });

122 123 124
    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);
125 126 127 128 129 130

    // Tap above the bottom sheet to dismiss it.
    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsNothing);
131 132
  });

133
  testWidgets('Verify that the BottomSheet animates non-linearly', (WidgetTester tester) async {
134
    late BuildContext savedContext;
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    showModalBottomSheet<void>(
      context: savedContext,
      builder: (BuildContext context) => const Text('BottomSheet'),
    );
    await tester.pump();

    await _checkNonLinearAnimation(tester);
    await tester.pumpAndSettle();

    // Tap above the bottom sheet to dismiss it.
    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pump();
    await _checkNonLinearAnimation(tester);
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(find.text('BottomSheet'), findsNothing);
  });

165
  testWidgets('Tapping outside a modal BottomSheet should not dismiss it when isDismissible=false', (WidgetTester tester) async {
166
    late BuildContext savedContext;
167

168 169 170
    await tester.pumpWidget(
      MaterialApp(
        home: Builder(
171 172 173
          builder: (BuildContext context) {
            savedContext = context;
            return Container();
174 175
          },
        ),
176
      ),
177
    );
178 179

    await tester.pump();
180 181
    expect(find.text('BottomSheet'), findsNothing);

182
    bool showBottomSheetThenCalled = false;
183
    showModalBottomSheet<void>(
184
      context: savedContext,
185
      builder: (BuildContext context) => const Text('BottomSheet'),
186
      isDismissible: false,
187
    ).then<void>((void value) {
188 189
      showBottomSheetThenCalled = true;
    });
190 191

    await tester.pumpAndSettle();
192
    expect(find.text('BottomSheet'), findsOneWidget);
193
    expect(showBottomSheetThenCalled, isFalse);
194

195
    // Tap above the bottom sheet, attempting to dismiss it.
196
    await tester.tapAt(const Offset(20.0, 20.0));
197 198 199
    await tester.pumpAndSettle(); // Bottom sheet should not dismiss.
    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsOneWidget);
200
  });
201

202
  testWidgets('Swiping down a modal BottomSheet should dismiss it by default', (WidgetTester tester) async {
203
    late BuildContext savedContext;
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    bool showBottomSheetThenCalled = false;
    showModalBottomSheet<void>(
      context: savedContext,
      isDismissible: false,
      builder: (BuildContext context) => const Text('BottomSheet'),
    ).then<void>((void value) {
      showBottomSheetThenCalled = true;
    });

    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);

    // Swipe the bottom sheet to dismiss it.
    await tester.drag(find.text('BottomSheet'), const Offset(0.0, 150.0));
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsNothing);
  });

  testWidgets('Swiping down a modal BottomSheet should not dismiss it when enableDrag is false', (WidgetTester tester) async {
238
    late BuildContext savedContext;
239 240 241 242 243 244 245 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 272 273

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    bool showBottomSheetThenCalled = false;
    showModalBottomSheet<void>(
      context: savedContext,
      isDismissible: false,
      enableDrag: false,
      builder: (BuildContext context) => const Text('BottomSheet'),
    ).then<void>((void value) {
      showBottomSheetThenCalled = true;
    });

    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);

    // Swipe the bottom sheet, attempting to dismiss it.
    await tester.drag(find.text('BottomSheet'), const Offset(0.0, 150.0));
    await tester.pumpAndSettle(); // Bottom sheet should not dismiss.
    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsOneWidget);
  });

  testWidgets('Swiping down a modal BottomSheet should dismiss it when enableDrag is true', (WidgetTester tester) async {
274
    late BuildContext savedContext;
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    await tester.pump();
    expect(find.text('BottomSheet'), findsNothing);

    bool showBottomSheetThenCalled = false;
    showModalBottomSheet<void>(
      context: savedContext,
      isDismissible: false,
      enableDrag: true,
      builder: (BuildContext context) => const Text('BottomSheet'),
    ).then<void>((void value) {
      showBottomSheetThenCalled = true;
    });

    await tester.pumpAndSettle();
    expect(find.text('BottomSheet'), findsOneWidget);
    expect(showBottomSheetThenCalled, isFalse);

    // Swipe the bottom sheet to dismiss it.
    await tester.drag(find.text('BottomSheet'), const Offset(0.0, 150.0));
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsNothing);
  });

309
  testWidgets('Modal BottomSheet builder should only be called once', (WidgetTester tester) async {
310
    late BuildContext savedContext;
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

    await tester.pumpWidget(MaterialApp(
      home: Builder(
        builder: (BuildContext context) {
          savedContext = context;
          return Container();
        },
      ),
    ));

    int numBuilderCalls = 0;
    showModalBottomSheet<void>(
      context: savedContext,
      isDismissible: false,
      enableDrag: true,
      builder: (BuildContext context) {
        numBuilderCalls++;
        return const Text('BottomSheet');
      },
    );

    await tester.pumpAndSettle();
    expect(numBuilderCalls, 1);

    // Swipe the bottom sheet to dismiss it.
    await tester.drag(find.text('BottomSheet'), const Offset(0.0, 150.0));
    await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
    expect(numBuilderCalls, 1);
  });

341
  testWidgets('Verify that a downwards fling dismisses a persistent BottomSheet', (WidgetTester tester) async {
342
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
343 344
    bool showBottomSheetThenCalled = false;

345 346
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
347
        key: scaffoldKey,
348 349
        body: const Center(child: Text('body')),
      ),
350 351 352 353 354
    ));

    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsNothing);

355
    scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) {
356
      return Container(
357
        margin: const EdgeInsets.all(40.0),
358
        child: const Text('BottomSheet'),
359
      );
360
    }).closed.whenComplete(() {
361 362
      showBottomSheetThenCalled = true;
    });
363

364 365
    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsNothing);
366

367
    await tester.pump(); // bottom sheet show animation starts
368

369 370
    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsOneWidget);
371

372
    await tester.pump(const Duration(seconds: 1)); // animation done
373

374 375
    expect(showBottomSheetThenCalled, isFalse);
    expect(find.text('BottomSheet'), findsOneWidget);
376

377 378
    // The fling below must be such that the velocity estimation examines an
    // offset greater than the kTouchSlop. Too slow or too short a distance, and
379
    // it won't trigger. Also, it must not be so much that it drags the bottom
380 381
    // sheet off the screen, or we won't see it after we pump!
    await tester.fling(find.text('BottomSheet'), const Offset(0.0, 50.0), 2000.0);
382
    await tester.pump(); // drain the microtask queue (Future completion callback)
383

384 385
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsOneWidget);
386

387
    await tester.pump(); // bottom sheet dismiss animation starts
388

389 390
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsOneWidget);
391

392
    await tester.pump(const Duration(seconds: 1)); // animation done
393

394 395
    expect(showBottomSheetThenCalled, isTrue);
    expect(find.text('BottomSheet'), findsNothing);
396 397
  });

398 399
  testWidgets('Verify that dragging past the bottom dismisses a persistent BottomSheet', (WidgetTester tester) async {
    // This is a regression test for https://github.com/flutter/flutter/issues/5528
400
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
401

402 403
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
404
        key: scaffoldKey,
405 406
        body: const Center(child: Text('body')),
      ),
407 408
    ));

409
    scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) {
410
      return Container(
411
        margin: const EdgeInsets.all(40.0),
412
        child: const Text('BottomSheet'),
413 414 415 416
      );
    });

    await tester.pump(); // bottom sheet show animation starts
417
    await tester.pump(const Duration(seconds: 1)); // animation done
418 419 420 421 422
    expect(find.text('BottomSheet'), findsOneWidget);

    await tester.fling(find.text('BottomSheet'), const Offset(0.0, 400.0), 1000.0);
    await tester.pump(); // drain the microtask queue (Future completion callback)
    await tester.pump(); // bottom sheet dismiss animation starts
423
    await tester.pump(const Duration(seconds: 1)); // animation done
424 425 426

    expect(find.text('BottomSheet'), findsNothing);
  });
427 428

  testWidgets('modal BottomSheet has no top MediaQuery', (WidgetTester tester) async {
429 430
    late BuildContext outerContext;
    late BuildContext innerContext;
431

432
    await tester.pumpWidget(Localizations(
433
      locale: const Locale('en', 'US'),
434
      delegates: const <LocalizationsDelegate<dynamic>>[
435 436 437
        DefaultWidgetsLocalizations.delegate,
        DefaultMaterialLocalizations.delegate,
      ],
438
      child: Directionality(
439
        textDirection: TextDirection.ltr,
440
        child: MediaQuery(
441
          data: const MediaQueryData(
442
            padding: EdgeInsets.all(50.0),
443
            size: Size(400.0, 600.0),
444
          ),
445
          child: Navigator(
446
            onGenerateRoute: (_) {
447
              return PageRouteBuilder<void>(
448 449
                pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
                  outerContext = context;
450
                  return Container();
451 452 453 454
                },
              );
            },
          ),
455 456 457 458
        ),
      ),
    ));

459
    showModalBottomSheet<void>(
460 461 462
      context: outerContext,
      builder: (BuildContext context) {
        innerContext = context;
463
        return Container();
464 465 466 467 468 469
      },
    );
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(
470
      MediaQuery.of(outerContext).padding,
471 472 473
      const EdgeInsets.all(50.0),
    );
    expect(
474
      MediaQuery.of(innerContext).padding,
475 476 477
      const EdgeInsets.only(left: 50.0, right: 50.0, bottom: 50.0),
    );
  });
478 479

  testWidgets('modal BottomSheet has semantics', (WidgetTester tester) async {
480 481
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
482

483 484
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
485
        key: scaffoldKey,
486 487
        body: const Center(child: Text('body')),
      ),
488 489 490
    ));


491
    showModalBottomSheet<void>(context: scaffoldKey.currentContext!, builder: (BuildContext context) {
492
      return Container(
493
        child: const Text('BottomSheet'),
494 495 496 497 498 499
      );
    });

    await tester.pump(); // bottom sheet show animation starts
    await tester.pump(const Duration(seconds: 1)); // animation done

500
    expect(semantics, hasSemantics(TestSemantics.root(
501
      children: <TestSemantics>[
502
        TestSemantics.rootChild(
503
          children: <TestSemantics>[
504
            TestSemantics(
505
              children: <TestSemantics>[
506
                TestSemantics(
507
                  label: 'Dialog',
508
                  textDirection: TextDirection.ltr,
509 510 511 512 513 514 515 516 517 518
                  flags: <SemanticsFlag>[
                    SemanticsFlag.scopesRoute,
                    SemanticsFlag.namesRoute,
                  ],
                  children: <TestSemantics>[
                    TestSemantics(
                      label: 'BottomSheet',
                      textDirection: TextDirection.ltr,
                    ),
                  ],
519 520 521
                ),
              ],
            ),
522
            TestSemantics(),
523 524 525 526 527 528
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreRect: true, ignoreId: true));
    semantics.dispose();
  });
529

530 531 532 533 534
  testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async {
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
    const Color color = Colors.pink;
    const double elevation = 9.0;
    final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12));
535
    const Clip clipBehavior = Clip.antiAlias;
536
    const Color barrierColor = Colors.red;
537 538 539 540 541 542 543 544 545

    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        key: scaffoldKey,
        body: const Center(child: Text('body')),
      ),
    ));

    showModalBottomSheet<void>(
546
      context: scaffoldKey.currentContext!,
547
      backgroundColor: color,
548
      barrierColor: barrierColor,
549 550
      elevation: elevation,
      shape: shape,
551
      clipBehavior: clipBehavior,
552 553 554 555 556 557 558 559 560 561 562 563 564 565
      builder: (BuildContext context) {
        return Container(
          child: const Text('BottomSheet'),
        );
      },
    );

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet));
    expect(bottomSheet.backgroundColor, color);
    expect(bottomSheet.elevation, elevation);
    expect(bottomSheet.shape, shape);
566
    expect(bottomSheet.clipBehavior, clipBehavior);
567 568 569

    final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last);
    expect(modalBarrier.color, barrierColor);
570 571
  });

572 573 574 575 576 577 578
  testWidgets('modal BottomSheet with scrollController has semantics', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        key: scaffoldKey,
579
        body: const Center(child: Text('body')),
580
      ),
581 582 583 584
    ));


    showModalBottomSheet<void>(
585
      context: scaffoldKey.currentContext!,
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
      builder: (BuildContext context) {
        return DraggableScrollableSheet(
          expand: false,
          builder: (_, ScrollController controller) {
            return SingleChildScrollView(
              controller: controller,
              child: Container(
                child: const Text('BottomSheet'),
              ),
            );
          },
        );
      },
    );

    await tester.pump(); // bottom sheet show animation starts
    await tester.pump(const Duration(seconds: 1)); // animation done

    expect(semantics, hasSemantics(TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          children: <TestSemantics>[
            TestSemantics(
              children: <TestSemantics>[
                TestSemantics(
611 612 613 614 615 616
                  label: 'Dialog',
                  textDirection: TextDirection.ltr,
                  flags: <SemanticsFlag>[
                    SemanticsFlag.scopesRoute,
                    SemanticsFlag.namesRoute,
                  ],
617 618
                  children: <TestSemantics>[
                    TestSemantics(
619 620 621 622 623 624 625 626
                      flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
                      actions: <SemanticsAction>[SemanticsAction.scrollDown, SemanticsAction.scrollUp],
                      children: <TestSemantics>[
                        TestSemantics(
                          label: 'BottomSheet',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
627 628 629 630 631
                    ),
                  ],
                ),
              ],
            ),
632
            TestSemantics(),
633 634 635 636 637 638
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreRect: true, ignoreId: true));
    semantics.dispose();
  });
639 640 641 642 643 644 645 646 647 648 649

  testWidgets('showModalBottomSheet does not use root Navigator by default', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Navigator(onGenerateRoute: (RouteSettings settings) => MaterialPageRoute<void>(builder: (_) {
          return const _TestPage();
        })),
        bottomNavigationBar: BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.ac_unit),
650
              label: 'Item 1',
651 652 653
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.style),
654
              label: 'Item 2',
655
            ),
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
          ],
        ),
      ),
    ));

    await tester.tap(find.text('Show bottom sheet'));
    await tester.pumpAndSettle();

    // Bottom sheet is displayed in correct position within the inner navigator
    // and above the BottomNavigationBar.
    expect(tester.getBottomLeft(find.byType(BottomSheet)).dy, 544.0);
  });

  testWidgets('showModalBottomSheet uses root Navigator when specified', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Navigator(onGenerateRoute: (RouteSettings settings) => MaterialPageRoute<void>(builder: (_) {
          return const _TestPage(useRootNavigator: true);
        })),
        bottomNavigationBar: BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.ac_unit),
679
              label: 'Item 1',
680 681 682
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.style),
683
              label: 'Item 2',
684
            ),
685 686 687 688 689 690 691 692 693 694 695 696
          ],
        ),
      ),
    ));

    await tester.tap(find.text('Show bottom sheet'));
    await tester.pumpAndSettle();

    // Bottom sheet is displayed in correct position above all content including
    // the BottomNavigationBar.
    expect(tester.getBottomLeft(find.byType(BottomSheet)).dy, 600.0);
  });
697 698 699 700 701 702 703 704 705 706 707 708 709 710

  testWidgets('Verify that route settings can be set in the showModalBottomSheet',
      (WidgetTester tester) async {
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
    const RouteSettings routeSettings =
        RouteSettings(name: 'route_name', arguments: 'route_argument');

    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        key: scaffoldKey,
        body: const Center(child: Text('body')),
      ),
    ));

711
    late RouteSettings retrievedRouteSettings;
712 713

    showModalBottomSheet<void>(
714
      context: scaffoldKey.currentContext!,
715 716
      routeSettings: routeSettings,
      builder: (BuildContext context) {
717
        retrievedRouteSettings = ModalRoute.of(context)!.settings;
718 719 720 721 722 723 724 725 726 727 728
        return Container(
          child: const Text('BottomSheet'),
        );
      },
    );

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(retrievedRouteSettings, routeSettings);
  });
729 730 731
}

class _TestPage extends StatelessWidget {
732
  const _TestPage({Key? key, this.useRootNavigator}) : super(key: key);
733

734
  final bool? useRootNavigator;
735 736 737 738

  @override
  Widget build(BuildContext context) {
    return Center(
739
      child: TextButton(
740 741 742 743
        child: const Text('Show bottom sheet'),
        onPressed: () {
          if (useRootNavigator != null) {
            showModalBottomSheet<void>(
744
              useRootNavigator: useRootNavigator!,
745 746 747 748 749 750 751 752 753
              context: context,
              builder: (_) => const Text('Modal bottom sheet'),
            );
          } else {
            showModalBottomSheet<void>(
              context: context,
              builder: (_) => const Text('Modal bottom sheet'),
            );
          }
754
        },
755 756 757
      ),
    );
  }
758
}