scaffold_test.dart 49.9 KB
Newer Older
1 2 3 4
// Copyright 2015 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
import 'package:flutter/foundation.dart';
6 7
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
8
import 'package:flutter_test/flutter_test.dart';
9
import 'package:flutter/gestures.dart' show DragStartBehavior;
10

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

13
void main() {
14
  testWidgets('Scaffold control test', (WidgetTester tester) async {
15
    final Key bodyKey = UniqueKey();
16 17 18 19 20 21 22 23 24 25 26 27 28 29
    Widget boilerplate(Widget child) {
      return Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: child,
        ),
      );
    }
    await tester.pumpWidget(boilerplate(Scaffold(
30 31
        appBar: AppBar(title: const Text('Title')),
        body: Container(key: bodyKey),
32
      ),
33
    ));
34
    expect(tester.takeException(), isFlutterError);
35

36 37 38 39
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Title')),
        body: Container(key: bodyKey),
40 41
      ),
    ));
42
    RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey));
43
    expect(bodyBox.size, equals(const Size(800.0, 544.0)));
44

45
    await tester.pumpWidget(boilerplate(MediaQuery(
46
        data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
47 48 49
        child: Scaffold(
          appBar: AppBar(title: const Text('Title')),
          body: Container(key: bodyKey),
50 51
        ),
      ),
52
    ));
53

54
    bodyBox = tester.renderObject(find.byKey(bodyKey));
55
    expect(bodyBox.size, equals(const Size(800.0, 444.0)));
56

57 58 59 60 61
    await tester.pumpWidget(boilerplate(MediaQuery(
      data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
      child: Scaffold(
        appBar: AppBar(title: const Text('Title')),
        body: Container(key: bodyKey),
62 63 64 65 66 67 68 69 70 71 72 73 74
        resizeToAvoidBottomInset: false,
      ),
    )));

    bodyBox = tester.renderObject(find.byKey(bodyKey));
    expect(bodyBox.size, equals(const Size(800.0, 544.0)));

    // Backwards compatiblity: deprecated resizeToAvoidBottomPadding flag
    await tester.pumpWidget(boilerplate(MediaQuery(
      data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
      child: Scaffold(
        appBar: AppBar(title: const Text('Title')),
        body: Container(key: bodyKey),
75
        resizeToAvoidBottomPadding: false,
76
      ),
77
    )));
78

79
    bodyBox = tester.renderObject(find.byKey(bodyKey));
80
    expect(bodyBox.size, equals(const Size(800.0, 544.0)));
81
  });
82

83
  testWidgets('Scaffold large bottom padding test', (WidgetTester tester) async {
84
    final Key bodyKey = UniqueKey();
85 86 87 88 89 90 91 92 93 94 95

    Widget boilerplate(Widget child) {
      return Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: child,
96
        ),
97 98 99 100 101 102
      );
    }

    await tester.pumpWidget(boilerplate(MediaQuery(
      data: const MediaQueryData(
        viewInsets: EdgeInsets.only(bottom: 700.0),
103
      ),
104 105 106
      child: Scaffold(
        body: Container(key: bodyKey),
      ))
107 108
    ));

109
    final RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey));
110 111
    expect(bodyBox.size, equals(const Size(800.0, 0.0)));

112
    await tester.pumpWidget(boilerplate(MediaQuery(
113
        data: const MediaQueryData(
114
          viewInsets: EdgeInsets.only(bottom: 500.0),
115
        ),
116 117
        child: Scaffold(
          body: Container(key: bodyKey),
118
        ),
119 120 121 122 123
      ),
    ));

    expect(bodyBox.size, equals(const Size(800.0, 100.0)));

124
    await tester.pumpWidget(boilerplate(MediaQuery(
125
        data: const MediaQueryData(
126
          viewInsets: EdgeInsets.only(bottom: 580.0),
127
        ),
128 129
        child: Scaffold(
          appBar: AppBar(
130 131
            title: const Text('Title'),
          ),
132
          body: Container(key: bodyKey),
133 134 135 136 137 138 139
        ),
      ),
    ));

    expect(bodyBox.size, equals(const Size(800.0, 0.0)));
  });

140
  testWidgets('Floating action entrance/exit animation', (WidgetTester tester) async {
141
    await tester.pumpWidget(const MaterialApp(home: Scaffold(
142 143
      floatingActionButton: FloatingActionButton(
        key: Key('one'),
144
        onPressed: null,
145
        child: Text('1'),
146
      ),
147
    )));
148 149 150

    expect(tester.binding.transientCallbackCount, 0);

151
    await tester.pumpWidget(const MaterialApp(home: Scaffold(
152 153
      floatingActionButton: FloatingActionButton(
        key: Key('two'),
154
        onPressed: null,
155
        child: Text('2'),
156
      ),
157
    )));
158 159

    expect(tester.binding.transientCallbackCount, greaterThan(0));
160
    await tester.pumpWidget(Container());
161
    expect(tester.binding.transientCallbackCount, 0);
162

163
    await tester.pumpWidget(const MaterialApp(home: Scaffold()));
164

165 166
    expect(tester.binding.transientCallbackCount, 0);

167
    await tester.pumpWidget(const MaterialApp(home: Scaffold(
168 169
      floatingActionButton: FloatingActionButton(
        key: Key('one'),
170
        onPressed: null,
171
        child: Text('1'),
172
      ),
173
    )));
174 175 176

    expect(tester.binding.transientCallbackCount, greaterThan(0));
  });
177

178
  testWidgets('Floating action button directionality', (WidgetTester tester) async {
179
    Widget build(TextDirection textDirection) {
180
      return Directionality(
181
        textDirection: textDirection,
182
        child: const MediaQuery(
183 184
          data: MediaQueryData(
            viewInsets: EdgeInsets.only(bottom: 200.0),
185
          ),
186 187
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
188
              onPressed: null,
189
              child: Text('1'),
190 191 192 193 194 195 196 197 198 199 200
            ),
          ),
        ),
      );
    }

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

    expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0));

    await tester.pumpWidget(build(TextDirection.rtl));
201
    expect(tester.binding.transientCallbackCount, 0);
202 203 204 205

    expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 356.0));
  });

206
  testWidgets('Drawer scrolling', (WidgetTester tester) async {
207
    final Key drawerKey = UniqueKey();
208 209
    const double appBarHeight = 256.0;

210
    final ScrollController scrollOffset = ScrollController();
211

212
    await tester.pumpWidget(
213 214 215
      MaterialApp(
        home: Scaffold(
          drawer: Drawer(
216
            key: drawerKey,
217
            child: ListView(
218
              dragStartBehavior: DragStartBehavior.down,
219
              controller: scrollOffset,
220
              children: List<Widget>.generate(10,
221 222 223
                (int index) => SizedBox(height: 100.0, child: Text('D$index')),
              ),
            ),
224
          ),
225
          body: CustomScrollView(
226
            slivers: <Widget>[
227
              const SliverAppBar(
228 229
                pinned: true,
                expandedHeight: appBarHeight,
230 231
                title: Text('Title'),
                flexibleSpace: FlexibleSpaceBar(title: Text('Title')),
232
              ),
233
              SliverPadding(
234
                padding: const EdgeInsets.only(top: appBarHeight),
235 236 237
                sliver: SliverList(
                  delegate: SliverChildListDelegate(List<Widget>.generate(
                    10, (int index) => SizedBox(height: 100.0, child: Text('B$index')),
238 239 240 241
                  )),
                ),
              ),
            ],
242
          ),
243
        ),
244 245 246
      )
    );

247
    final ScaffoldState state = tester.firstState(find.byType(Scaffold));
248 249 250 251 252
    state.openDrawer();

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

253
    expect(scrollOffset.offset, 0.0);
254 255

    const double scrollDelta = 80.0;
256
    await tester.drag(find.byKey(drawerKey), const Offset(0.0, -scrollDelta));
257 258
    await tester.pump();

259
    expect(scrollOffset.offset, scrollDelta);
260

261
    final RenderBox renderBox = tester.renderObject(find.byType(AppBar));
262 263
    expect(renderBox.size.height, equals(appBarHeight));
  });
264

265
  Widget _buildStatusBarTestApp(TargetPlatform platform) {
266 267 268
    return MaterialApp(
      theme: ThemeData(platform: platform),
      home: MediaQuery(
269
        data: const MediaQueryData(padding: EdgeInsets.only(top: 25.0)), // status bar
270 271
        child: Scaffold(
          body: CustomScrollView(
272 273
            primary: true,
            slivers: <Widget>[
274
              const SliverAppBar(
275
                title: Text('Title'),
276
              ),
277 278 279
              SliverList(
                delegate: SliverChildListDelegate(List<Widget>.generate(
                  20, (int index) => SizedBox(height: 100.0, child: Text('$index')),
280 281 282 283 284 285
                )),
              ),
            ],
          ),
        ),
      ),
286
    );
287
  }
288

289 290
  testWidgets('Tapping the status bar scrolls to top on iOS', (WidgetTester tester) async {
    await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.iOS));
Adam Barth's avatar
Adam Barth committed
291
    final ScrollableState scrollable = tester.state(find.byType(Scrollable));
292 293
    scrollable.position.jumpTo(500.0);
    expect(scrollable.position.pixels, equals(500.0));
294
    await tester.tapAt(const Offset(100.0, 10.0));
295
    await tester.pumpAndSettle();
296
    expect(scrollable.position.pixels, equals(0.0));
297 298 299
  });

  testWidgets('Tapping the status bar does not scroll to top on Android', (WidgetTester tester) async {
300
    await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.android));
Adam Barth's avatar
Adam Barth committed
301
    final ScrollableState scrollable = tester.state(find.byType(Scrollable));
302 303
    scrollable.position.jumpTo(500.0);
    expect(scrollable.position.pixels, equals(500.0));
304
    await tester.tapAt(const Offset(100.0, 10.0));
305
    await tester.pump();
306
    await tester.pump(const Duration(seconds: 1));
307
    expect(scrollable.position.pixels, equals(500.0));
308
  });
309 310

  testWidgets('Bottom sheet cannot overlap app bar', (WidgetTester tester) async {
311
    final Key sheetKey = UniqueKey();
312 313

    await tester.pumpWidget(
314 315 316 317
      MaterialApp(
        theme: ThemeData(platform: TargetPlatform.android),
        home: Scaffold(
          appBar: AppBar(
318
            title: const Text('Title'),
319
          ),
320
          body: Builder(
321
            builder: (BuildContext context) {
322
              return GestureDetector(
323
                onTap: () {
324
                  Scaffold.of(context).showBottomSheet<void>((BuildContext context) {
325
                    return Container(
326
                      key: sheetKey,
327
                      color: Colors.blue[500],
328 329 330
                    );
                  });
                },
331
                child: const Text('X'),
332
              );
333 334 335 336
            },
          ),
        ),
      ),
337 338 339 340 341
    );
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(seconds: 1));

342 343
    final RenderBox appBarBox = tester.renderObject(find.byType(AppBar));
    final RenderBox sheetBox = tester.renderObject(find.byKey(sheetKey));
344

345 346
    final Offset appBarBottomRight = appBarBox.localToGlobal(appBarBox.size.bottomRight(Offset.zero));
    final Offset sheetTopRight = sheetBox.localToGlobal(sheetBox.size.topRight(Offset.zero));
347 348 349

    expect(appBarBottomRight, equals(sheetTopRight));
  });
350

351 352 353
  testWidgets('Persistent bottom buttons are persistent', (WidgetTester tester) async {
    bool didPressButton = false;
    await tester.pumpWidget(
354 355 356 357
      MaterialApp(
        home: Scaffold(
          body: SingleChildScrollView(
            child: Container(
358
              color: Colors.amber[500],
359
              height: 5000.0,
360
              child: const Text('body'),
361 362 363
            ),
          ),
          persistentFooterButtons: <Widget>[
364
            FlatButton(
365 366 367
              onPressed: () {
                didPressButton = true;
              },
368
              child: const Text('X'),
369
            ),
370 371 372 373 374
          ],
        ),
      ),
    );

375
    await tester.drag(find.text('body'), const Offset(0.0, -1000.0));
376 377 378 379 380
    expect(didPressButton, isFalse);
    await tester.tap(find.text('X'));
    expect(didPressButton, isTrue);
  });

381 382
  testWidgets('Persistent bottom buttons apply media padding', (WidgetTester tester) async {
    await tester.pumpWidget(
383
      Directionality(
384
        textDirection: TextDirection.ltr,
385
        child: MediaQuery(
386
          data: const MediaQueryData(
387
            padding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0),
388
          ),
389 390 391
          child: Scaffold(
            body: SingleChildScrollView(
              child: Container(
392 393 394 395 396
                color: Colors.amber[500],
                height: 5000.0,
                child: const Text('body'),
              ),
            ),
397
            persistentFooterButtons: const <Widget>[Placeholder()],
398 399 400 401 402 403 404 405
          ),
        ),
      ),
    );
    expect(tester.getBottomLeft(find.byType(ButtonBar)), const Offset(10.0, 560.0));
    expect(tester.getBottomRight(find.byType(ButtonBar)), const Offset(770.0, 560.0));
  });

406
  group('back arrow', () {
407
    Future<void> expectBackIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon) async {
408
      final GlobalKey rootKey = GlobalKey();
409
      final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
410 411 412
        '/': (_) => Container(key: rootKey, child: const Text('Home')),
        '/scaffold': (_) => Scaffold(
            appBar: AppBar(),
413
            body: const Text('Scaffold'),
414
        ),
415 416
      };
      await tester.pumpWidget(
417
        MaterialApp(theme: ThemeData(platform: platform), routes: routes)
418 419 420 421 422 423
      );

      Navigator.pushNamed(rootKey.currentContext, '/scaffold');
      await tester.pump();
      await tester.pump(const Duration(seconds: 1));

424
      final Icon icon = tester.widget(find.byType(Icon));
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
      expect(icon.icon, expectedIcon);
    }

    testWidgets('Back arrow uses correct default on Android', (WidgetTester tester) async {
      await expectBackIcon(tester, TargetPlatform.android, Icons.arrow_back);
    });

    testWidgets('Back arrow uses correct default on Fuchsia', (WidgetTester tester) async {
      await expectBackIcon(tester, TargetPlatform.fuchsia, Icons.arrow_back);
    });

    testWidgets('Back arrow uses correct default on iOS', (WidgetTester tester) async {
      await expectBackIcon(tester, TargetPlatform.iOS, Icons.arrow_back_ios);
    });
  });
440

441
  group('close button', () {
442
    Future<void> expectCloseIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon, PageRoute<void> routeBuilder()) async {
443
      await tester.pumpWidget(
444 445 446
        MaterialApp(
          theme: ThemeData(platform: platform),
          home: Scaffold(appBar: AppBar(), body: const Text('Page 1')),
447 448 449
        )
      );

450
      tester.state<NavigatorState>(find.byType(Navigator)).push(routeBuilder());
451 452 453 454 455 456

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

      final Icon icon = tester.widget(find.byType(Icon));
      expect(icon.icon, expectedIcon);
457 458 459 460
      expect(find.byType(CloseButton), findsOneWidget);
    }

    PageRoute<void> materialRouteBuilder() {
461
      return MaterialPageRoute<void>(
462
        builder: (BuildContext context) {
463
          return Scaffold(appBar: AppBar(), body: const Text('Page 2'));
464 465 466 467 468 469
        },
        fullscreenDialog: true,
      );
    }

    PageRoute<void> customPageRouteBuilder() {
470
      return _CustomPageRoute<void>(
471
        builder: (BuildContext context) {
472
          return Scaffold(appBar: AppBar(), body: const Text('Page 2'));
473 474 475
        },
        fullscreenDialog: true,
      );
476 477 478
    }

    testWidgets('Close button shows correctly on Android', (WidgetTester tester) async {
479
      await expectCloseIcon(tester, TargetPlatform.android, Icons.close, materialRouteBuilder);
480 481 482
    });

    testWidgets('Close button shows correctly on Fuchsia', (WidgetTester tester) async {
483
      await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, materialRouteBuilder);
484 485 486
    });

    testWidgets('Close button shows correctly on iOS', (WidgetTester tester) async {
487 488 489 490 491 492 493 494 495 496 497 498 499
      await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, materialRouteBuilder);
    });

    testWidgets('Close button shows correctly with custom page route on Android', (WidgetTester tester) async {
      await expectCloseIcon(tester, TargetPlatform.android, Icons.close, customPageRouteBuilder);
    });

    testWidgets('Close button shows correctly with custom page route on Fuchsia', (WidgetTester tester) async {
      await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, customPageRouteBuilder);
    });

    testWidgets('Close button shows correctly with custom page route on iOS', (WidgetTester tester) async {
      await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, customPageRouteBuilder);
500 501 502
    });
  });

503 504
  group('body size', () {
    testWidgets('body size with container', (WidgetTester tester) async {
505 506
      final Key testKey = UniqueKey();
      await tester.pumpWidget(Directionality(
507
        textDirection: TextDirection.ltr,
508
        child: MediaQuery(
509
          data: const MediaQueryData(),
510 511
          child: Scaffold(
            body: Container(
512 513 514 515
              key: testKey,
            ),
          ),
        ),
516
      ));
517
      expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0));
518
      expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
519 520 521
    });

    testWidgets('body size with sized container', (WidgetTester tester) async {
522 523
      final Key testKey = UniqueKey();
      await tester.pumpWidget(Directionality(
524
        textDirection: TextDirection.ltr,
525
        child: MediaQuery(
526
          data: const MediaQueryData(),
527 528
          child: Scaffold(
            body: Container(
529 530 531 532 533
              key: testKey,
              height: 100.0,
            ),
          ),
        ),
534
      ));
535
      expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 100.0));
536
      expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
537 538 539
    });

    testWidgets('body size with centered container', (WidgetTester tester) async {
540 541
      final Key testKey = UniqueKey();
      await tester.pumpWidget(Directionality(
542
        textDirection: TextDirection.ltr,
543
        child: MediaQuery(
544
          data: const MediaQueryData(),
545 546 547
          child: Scaffold(
            body: Center(
              child: Container(
548 549 550 551 552
                key: testKey,
              ),
            ),
          ),
        ),
553
      ));
554
      expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0));
555
      expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
556 557 558
    });

    testWidgets('body size with button', (WidgetTester tester) async {
559 560
      final Key testKey = UniqueKey();
      await tester.pumpWidget(Directionality(
561
        textDirection: TextDirection.ltr,
562
        child: MediaQuery(
563
          data: const MediaQueryData(),
564 565
          child: Scaffold(
            body: FlatButton(
566 567 568 569 570 571
              key: testKey,
              onPressed: () { },
              child: const Text(''),
            ),
          ),
        ),
572
      ));
573
      expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 48.0));
574
      expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
575
    });
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631

    testWidgets('body size with extendBody', (WidgetTester tester) async {
      final Key bodyKey = UniqueKey();
      double mediaQueryBottom;

      Widget buildFrame({ bool extendBody, bool resizeToAvoidBottomInset, double viewInsetBottom = 0.0 }) {
        return Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: MediaQueryData(
              viewInsets: EdgeInsets.only(bottom: viewInsetBottom),
            ),
            child: Scaffold(
              resizeToAvoidBottomInset: resizeToAvoidBottomInset,
              extendBody: extendBody,
              body: Builder(
                builder: (BuildContext context) {
                  mediaQueryBottom = MediaQuery.of(context).padding.bottom;
                  return Container(key: bodyKey);
                },
              ),
              bottomNavigationBar: const BottomAppBar(
                child: SizedBox(height: 48.0,),
              ),
            ),
          ),
        );
      }

      await tester.pumpWidget(buildFrame(extendBody: true));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0));
      expect(mediaQueryBottom, 48.0);

      await tester.pumpWidget(buildFrame(extendBody: false));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0)); // 552 = 600 - 48 (BAB height)
      expect(mediaQueryBottom, 0.0);

      // If resizeToAvoidBottomInsets is false, same results as if it was unspecified (null).
      await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0));
      expect(mediaQueryBottom, 48.0);

      await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0));
      expect(mediaQueryBottom, 0.0);

      // If resizeToAvoidBottomInsets is true and viewInsets.bottom is > the bottom
      // navigation bar's height then the body always resizes and the MediaQuery
      // isn't adjusted. This case corresponds to the keyboard appearing.
      await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));
      expect(mediaQueryBottom, 0.0);

      await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0));
      expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));
      expect(mediaQueryBottom, 0.0);
632
    });
633
  });
634 635 636 637 638 639 640 641

  testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async {
    const String bodyLabel = 'I am the body';
    const String persistentFooterButtonLabel = 'a button on the bottom';
    const String bottomNavigationBarLabel = 'a bar in an app';
    const String floatingActionButtonLabel = 'I float in space';
    const String drawerLabel = 'I am the reason for this test';

642
    final SemanticsTester semantics = SemanticsTester(tester);
643
    await tester.pumpWidget(const MaterialApp(home: Scaffold(
644 645 646 647 648
      body: Text(bodyLabel),
      persistentFooterButtons: <Widget>[Text(persistentFooterButtonLabel)],
      bottomNavigationBar: Text(bottomNavigationBarLabel),
      floatingActionButton: Text(floatingActionButtonLabel),
      drawer: Drawer(child: Text(drawerLabel)),
649 650
    )));

651 652 653 654 655
    expect(semantics, includesNodeWith(label: bodyLabel));
    expect(semantics, includesNodeWith(label: persistentFooterButtonLabel));
    expect(semantics, includesNodeWith(label: bottomNavigationBarLabel));
    expect(semantics, includesNodeWith(label: floatingActionButtonLabel));
    expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
656 657 658 659 660 661

    final ScaffoldState state = tester.firstState(find.byType(Scaffold));
    state.openDrawer();
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

662 663 664 665 666
    expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
    expect(semantics, isNot(includesNodeWith(label: persistentFooterButtonLabel)));
    expect(semantics, isNot(includesNodeWith(label: bottomNavigationBarLabel)));
    expect(semantics, isNot(includesNodeWith(label: floatingActionButtonLabel)));
    expect(semantics, includesNodeWith(label: drawerLabel));
667 668 669

    semantics.dispose();
  });
Ian Hickson's avatar
Ian Hickson committed
670 671

  testWidgets('Scaffold and extreme window padding', (WidgetTester tester) async {
672 673 674 675 676 677 678 679 680 681 682 683
    final Key appBar = UniqueKey();
    final Key body = UniqueKey();
    final Key floatingActionButton = UniqueKey();
    final Key persistentFooterButton = UniqueKey();
    final Key drawer = UniqueKey();
    final Key bottomNavigationBar = UniqueKey();
    final Key insideAppBar = UniqueKey();
    final Key insideBody = UniqueKey();
    final Key insideFloatingActionButton = UniqueKey();
    final Key insidePersistentFooterButton = UniqueKey();
    final Key insideDrawer = UniqueKey();
    final Key insideBottomNavigationBar = UniqueKey();
Ian Hickson's avatar
Ian Hickson committed
684
    await tester.pumpWidget(
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
      Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.rtl,
          child: MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.only(
                left: 20.0,
                top: 30.0,
                right: 50.0,
                bottom: 60.0,
Ian Hickson's avatar
Ian Hickson committed
700
              ),
701
              viewInsets: EdgeInsets.only(bottom: 200.0),
Ian Hickson's avatar
Ian Hickson committed
702
            ),
703
            child: Scaffold(
704
              drawerDragStartBehavior: DragStartBehavior.down,
705 706 707 708 709 710 711 712
              appBar: PreferredSize(
                preferredSize: const Size(11.0, 13.0),
                child: Container(
                  key: appBar,
                  child: SafeArea(
                    child: Placeholder(key: insideAppBar),
                  ),
                ),
Ian Hickson's avatar
Ian Hickson committed
713
              ),
714 715 716 717 718
              body: Container(
                key: body,
                child: SafeArea(
                  child: Placeholder(key: insideBody),
                ),
Ian Hickson's avatar
Ian Hickson committed
719
              ),
720 721 722 723
              floatingActionButton: SizedBox(
                key: floatingActionButton,
                width: 77.0,
                height: 77.0,
724
                child: SafeArea(
725
                  child: Placeholder(key: insideFloatingActionButton),
Ian Hickson's avatar
Ian Hickson committed
726 727
                ),
              ),
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
              persistentFooterButtons: <Widget>[
                SizedBox(
                  key: persistentFooterButton,
                  width: 100.0,
                  height: 90.0,
                  child: SafeArea(
                    child: Placeholder(key: insidePersistentFooterButton),
                  ),
                ),
              ],
              drawer: Container(
                key: drawer,
                width: 204.0,
                child: SafeArea(
                  child: Placeholder(key: insideDrawer),
                ),
Ian Hickson's avatar
Ian Hickson committed
744
              ),
745 746 747 748 749 750
              bottomNavigationBar: SizedBox(
                key: bottomNavigationBar,
                height: 85.0,
                child: SafeArea(
                  child: Placeholder(key: insideBottomNavigationBar),
                ),
Ian Hickson's avatar
Ian Hickson committed
751 752 753 754 755 756 757 758 759 760 761
              ),
            ),
          ),
        ),
      ),
    );
    // open drawer
    await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

Dan Field's avatar
Dan Field committed
762 763 764 765 766 767 768 769 770 771 772 773
    expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
    expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 348.0));
    expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0)));
    expect(tester.getRect(find.byKey(persistentFooterButton)),const  Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); // Note: has 8px each top/bottom padding.
    expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
    expect(tester.getRect(find.byKey(bottomNavigationBar)), const Rect.fromLTRB(0.0, 515.0, 800.0, 600.0));
    expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
    expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 348.0));
    expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0)));
    expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 357.0, 128.0, 447.0));
    expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
    expect(tester.getRect(find.byKey(insideBottomNavigationBar)), const Rect.fromLTRB(20.0, 515.0, 750.0, 540.0));
774 775 776
  });

  testWidgets('Scaffold and extreme window padding - persistent footer buttons only', (WidgetTester tester) async {
777 778 779 780 781 782 783 784 785 786
    final Key appBar = UniqueKey();
    final Key body = UniqueKey();
    final Key floatingActionButton = UniqueKey();
    final Key persistentFooterButton = UniqueKey();
    final Key drawer = UniqueKey();
    final Key insideAppBar = UniqueKey();
    final Key insideBody = UniqueKey();
    final Key insideFloatingActionButton = UniqueKey();
    final Key insidePersistentFooterButton = UniqueKey();
    final Key insideDrawer = UniqueKey();
787
    await tester.pumpWidget(
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
      Localizations(
        locale: const Locale('en', 'us'),
        delegates: const <LocalizationsDelegate<dynamic>>[
          DefaultWidgetsLocalizations.delegate,
          DefaultMaterialLocalizations.delegate,
        ],
        child: Directionality(
          textDirection: TextDirection.rtl,
          child: MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.only(
                left: 20.0,
                top: 30.0,
                right: 50.0,
                bottom: 60.0,
803
              ),
804
              viewInsets: EdgeInsets.only(bottom: 200.0),
805
            ),
806 807 808 809 810 811 812 813 814
            child: Scaffold(
              appBar: PreferredSize(
                preferredSize: const Size(11.0, 13.0),
                child: Container(
                  key: appBar,
                  child: SafeArea(
                    child: Placeholder(key: insideAppBar),
                  ),
                ),
815
              ),
816 817 818 819 820
              body: Container(
                key: body,
                child: SafeArea(
                  child: Placeholder(key: insideBody),
                ),
821
              ),
822 823 824 825
              floatingActionButton: SizedBox(
                key: floatingActionButton,
                width: 77.0,
                height: 77.0,
826
                child: SafeArea(
827
                  child: Placeholder(key: insideFloatingActionButton),
828 829
                ),
              ),
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
              persistentFooterButtons: <Widget>[
                SizedBox(
                  key: persistentFooterButton,
                  width: 100.0,
                  height: 90.0,
                  child: SafeArea(
                    child: Placeholder(key: insidePersistentFooterButton),
                  ),
                ),
              ],
              drawer: Container(
                key: drawer,
                width: 204.0,
                child: SafeArea(
                  child: Placeholder(key: insideDrawer),
                ),
846 847 848 849 850 851 852 853 854 855 856
              ),
            ),
          ),
        ),
      ),
    );
    // open drawer
    await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

Dan Field's avatar
Dan Field committed
857 858 859 860 861 862 863 864 865 866
    expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
    expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 400.0));
    expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0)));
    expect(tester.getRect(find.byKey(persistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); // Note: has 8px each top/bottom padding.
    expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
    expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
    expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 400.0));
    expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0)));
    expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0));
    expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
Ian Hickson's avatar
Ian Hickson committed
867
  });
868 869


870 871
  group('ScaffoldGeometry', () {
    testWidgets('bottomNavigationBar', (WidgetTester tester) async {
872 873 874 875
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: Container(),
            bottomNavigationBar: ConstrainedBox(
876 877
              key: key,
              constraints: const BoxConstraints.expand(height: 80.0),
878
              child: _GeometryListener(),
879 880 881 882 883
            ),
      )));

      final RenderBox navigationBox = tester.renderObject(find.byKey(key));
      final RenderBox appBox = tester.renderObject(find.byType(MaterialApp));
884
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
885 886 887 888
      final ScaffoldGeometry geometry = listenerState.cache.value;

      expect(
        geometry.bottomNavigationBarTop,
889
        appBox.size.height - navigationBox.size.height,
890 891 892 893
      );
    });

    testWidgets('no bottomNavigationBar', (WidgetTester tester) async {
894 895
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: ConstrainedBox(
896
              constraints: const BoxConstraints.expand(height: 80.0),
897
              child: _GeometryListener(),
898 899 900
            ),
      )));

901
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
902 903 904 905
      final ScaffoldGeometry geometry = listenerState.cache.value;

      expect(
        geometry.bottomNavigationBarTop,
906
        null,
907 908 909 910
      );
    });

    testWidgets('floatingActionButton', (WidgetTester tester) async {
911 912 913 914
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: Container(),
            floatingActionButton: FloatingActionButton(
915
              key: key,
916
              child: _GeometryListener(),
917
              onPressed: () { },
918 919 920 921
            ),
      )));

      final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key));
922
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
923 924 925 926 927 928
      final ScaffoldGeometry geometry = listenerState.cache.value;

      final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size;

      expect(
        geometry.floatingActionButtonArea,
929
        fabRect,
930 931 932 933
      );
    });

    testWidgets('no floatingActionButton', (WidgetTester tester) async {
934 935
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: ConstrainedBox(
936
              constraints: const BoxConstraints.expand(height: 80.0),
937
              child: _GeometryListener(),
938 939 940
            ),
      )));

941
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
942 943 944 945
      final ScaffoldGeometry geometry = listenerState.cache.value;

      expect(
          geometry.floatingActionButtonArea,
946
          null,
947 948 949
      );
    });

950
    testWidgets('floatingActionButton entrance/exit animation', (WidgetTester tester) async {
951 952 953
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: ConstrainedBox(
954
              constraints: const BoxConstraints.expand(height: 80.0),
955
              child: _GeometryListener(),
956 957 958
            ),
      )));

959 960 961
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: Container(),
            floatingActionButton: FloatingActionButton(
962
              key: key,
963
              child: _GeometryListener(),
964
              onPressed: () { },
965 966 967
            ),
      )));

968
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
969 970
      await tester.pump(const Duration(milliseconds: 50));

971 972 973
      ScaffoldGeometry geometry = listenerState.cache.value;
      final Rect transitioningFabRect = geometry.floatingActionButtonArea;

974 975 976 977
      final double transitioningRotation = tester.widget<RotationTransition>(
        find.byType(RotationTransition),
      ).turns.value;

978 979 980 981 982
      await tester.pump(const Duration(seconds: 3));
      geometry = listenerState.cache.value;
      final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key));
      final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size;

983 984 985 986 987 988 989 990
      final double completedRotation = tester.widget<RotationTransition>(
        find.byType(RotationTransition),
      ).turns.value;

      expect(transitioningRotation, lessThan(1.0));

      expect(completedRotation, equals(1.0));

991 992
      expect(
        geometry.floatingActionButtonArea,
993
        fabRect,
994 995 996 997
      );

      expect(
        geometry.floatingActionButtonArea.center,
998
        transitioningFabRect.center,
999
      );
1000 1001

      expect(
1002
        geometry.floatingActionButtonArea.width,
1003
        greaterThan(transitioningFabRect.width),
1004 1005 1006 1007
      );

      expect(
        geometry.floatingActionButtonArea.height,
1008
        greaterThan(transitioningFabRect.height),
1009 1010 1011
      );
    });

1012
    testWidgets('change notifications', (WidgetTester tester) async {
1013
      final GlobalKey key = GlobalKey();
1014
      int numNotificationsAtLastFrame = 0;
1015 1016
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: ConstrainedBox(
1017
              constraints: const BoxConstraints.expand(height: 80.0),
1018
              child: _GeometryListener(),
1019 1020 1021
            ),
      )));

1022
      final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
1023 1024 1025 1026

      expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
      numNotificationsAtLastFrame = listenerState.numNotifications;

1027 1028 1029
      await tester.pumpWidget(MaterialApp(home: Scaffold(
            body: Container(),
            floatingActionButton: FloatingActionButton(
1030
              key: key,
1031
              child: _GeometryListener(),
1032
              onPressed: () { },
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
            ),
      )));

      expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
      numNotificationsAtLastFrame = listenerState.numNotifications;

      await tester.pump(const Duration(milliseconds: 50));

      expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
      numNotificationsAtLastFrame = listenerState.numNotifications;

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

      expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
      numNotificationsAtLastFrame = listenerState.numNotifications;
    });
1049

jslavitz's avatar
jslavitz committed
1050 1051 1052 1053 1054
    testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async {
      const String bodyLabel = 'I am the body';
      const String drawerLabel = 'I am the label on start side';
      const String endDrawerLabel = 'I am the label on end side';

1055
      final SemanticsTester semantics = SemanticsTester(tester);
1056
      await tester.pumpWidget(const MaterialApp(home: Scaffold(
1057 1058 1059
        body: Text(bodyLabel),
        drawer: Drawer(child: Text(drawerLabel)),
        endDrawer: Drawer(child: Text(endDrawerLabel)),
jslavitz's avatar
jslavitz committed
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
      )));

      expect(semantics, includesNodeWith(label: bodyLabel));
      expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
      expect(semantics, isNot(includesNodeWith(label: endDrawerLabel)));

      final ScaffoldState state = tester.firstState(find.byType(Scaffold));
      state.openDrawer();
      await tester.pump();
      await tester.pump(const Duration(seconds: 1));

      expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
      expect(semantics, includesNodeWith(label: drawerLabel));

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

      expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
      expect(semantics, includesNodeWith(label: endDrawerLabel));

      semantics.dispose();
    });

1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
    testWidgets('Drawer state query correctly', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: SafeArea(
            left: false,
            top: true,
            right: false,
            bottom: false,
            child: Scaffold(
              endDrawer: const Drawer(
                child: Text('endDrawer'),
              ),
              drawer: const Drawer(
                child: Text('drawer'),
              ),
              body: const Text('scaffold body'),
              appBar: AppBar(
                centerTitle: true,
1102
                title: const Text('Title'),
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
              ),
            ),
          ),
        ),
      );

      final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));

      final Finder drawerOpenButton = find.byType(IconButton).first;
      final Finder endDrawerOpenButton = find.byType(IconButton).last;

      await tester.tap(drawerOpenButton);
      await tester.pumpAndSettle();
      expect(true, scaffoldState.isDrawerOpen);
      await tester.tap(endDrawerOpenButton);
      await tester.pumpAndSettle();
      expect(false, scaffoldState.isDrawerOpen);

      await tester.tap(endDrawerOpenButton);
      await tester.pumpAndSettle();
      expect(true, scaffoldState.isEndDrawerOpen);
      await tester.tap(drawerOpenButton);
      await tester.pumpAndSettle();
      expect(false, scaffoldState.isEndDrawerOpen);
jslavitz's avatar
jslavitz committed
1127

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
      scaffoldState.openDrawer();
      expect(true, scaffoldState.isDrawerOpen);
      await tester.tap(drawerOpenButton);
      await tester.pumpAndSettle();

      scaffoldState.openEndDrawer();
      expect(true, scaffoldState.isEndDrawerOpen);
    });

    testWidgets('Dual Drawer Opening', (WidgetTester tester) async {
jslavitz's avatar
jslavitz committed
1138
      await tester.pumpWidget(
1139 1140
        MaterialApp(
          home: SafeArea(
jslavitz's avatar
jslavitz committed
1141 1142 1143 1144
            left: false,
            top: true,
            right: false,
            bottom: false,
1145
            child: Scaffold(
jslavitz's avatar
jslavitz committed
1146
              endDrawer: const Drawer(
1147
                child: Text('endDrawer'),
jslavitz's avatar
jslavitz committed
1148 1149
              ),
              drawer: const Drawer(
1150
                child: Text('drawer'),
jslavitz's avatar
jslavitz committed
1151 1152
              ),
              body: const Text('scaffold body'),
1153
              appBar: AppBar(
jslavitz's avatar
jslavitz committed
1154
                centerTitle: true,
1155 1156
                title: const Text('Title'),
              ),
jslavitz's avatar
jslavitz committed
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
            ),
          ),
        ),
      );

      // Open Drawer, tap on end drawer, which closes the drawer, but does
      // not open the drawer.
      await tester.tap(find.byType(IconButton).first);
      await tester.pumpAndSettle();
      await tester.tap(find.byType(IconButton).last);
      await tester.pumpAndSettle();

      expect(find.text('endDrawer'), findsNothing);
      expect(find.text('drawer'), findsNothing);

      // Tapping the first opens the first drawer
      await tester.tap(find.byType(IconButton).first);
      await tester.pumpAndSettle();

      expect(find.text('endDrawer'), findsNothing);
      expect(find.text('drawer'), findsOneWidget);

      // Tapping on the end drawer and then on the drawer should close the
      // drawer and then reopen it.
      await tester.tap(find.byType(IconButton).last);
      await tester.pumpAndSettle();
      await tester.tap(find.byType(IconButton).first);
      await tester.pumpAndSettle();

      expect(find.text('endDrawer'), findsNothing);
      expect(find.text('drawer'), findsOneWidget);
    });
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201

    testWidgets('Drawer opens correctly with padding from MediaQuery', (WidgetTester tester) async {
      // The padding described by MediaQuery is larger than the default
      // drawer drag zone width which is 20.
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            drawer: const Drawer(
              child: Text('drawer'),
            ),
            body: const Text('scaffold body'),
            appBar: AppBar(
              centerTitle: true,
1202
              title: const Text('Title'),
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
            ),
          ),
        ),
      );

      ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));

      expect(scaffoldState.isDrawerOpen, false);

      await tester.dragFrom(const Offset(35, 100), const Offset(300, 0));
      await tester.pumpAndSettle();

      expect(scaffoldState.isDrawerOpen, false);

      await tester.pumpWidget(
        MaterialApp(
          home: MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.fromLTRB(40, 0, 0, 0)
            ),
            child: Scaffold(
              drawer: const Drawer(
                child: Text('drawer'),
              ),
              body: const Text('scaffold body'),
              appBar: AppBar(
                centerTitle: true,
1230
                title: const Text('Title'),
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
              ),
            ),
          ),
        ),
      );

      scaffoldState = tester.state(find.byType(Scaffold));

      expect(scaffoldState.isDrawerOpen, false);

      await tester.dragFrom(const Offset(35, 100), const Offset(300, 0));
      await tester.pumpAndSettle();

      expect(scaffoldState.isDrawerOpen, true);
    });

    testWidgets('Drawer opens correctly with padding from MediaQuer (RTL)', (WidgetTester tester) async {
      // The padding described by MediaQuery is larger than the default
      // drawer drag zone width which is 20.
      await tester.pumpWidget(
        MaterialApp(
          home: MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.fromLTRB(0, 0, 40, 0)
            ),
            child: Directionality(
              textDirection: TextDirection.rtl,
              child: Scaffold(
                drawer: const Drawer(
                  child: Text('drawer'),
                ),
                body: const Text('scaffold body'),
                appBar: AppBar(
                  centerTitle: true,
1265
                  title: const Text('Title'),
1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281
                ),
              ),
            ),
          ),
        ),
      );

      final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));

      expect(scaffoldState.isDrawerOpen, false);

      await tester.dragFrom(const Offset(765, 100), const Offset(-300, 0));
      await tester.pumpAndSettle();

      expect(scaffoldState.isDrawerOpen, true);
    });
1282
  });
1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334

  testWidgets('Nested scaffold body insets', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/20295

    final Key bodyKey = UniqueKey();

    Widget buildFrame(bool innerResizeToAvoidBottomInset, bool outerResizeToAvoidBottomInset) {
      return Directionality(
        textDirection: TextDirection.ltr,
        child: MediaQuery(
          data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
          child: Builder(
            builder: (BuildContext context) {
              return Scaffold(
                resizeToAvoidBottomInset: outerResizeToAvoidBottomInset,
                body: Builder(
                  builder: (BuildContext context) {
                    return Scaffold(
                      resizeToAvoidBottomInset: innerResizeToAvoidBottomInset,
                      body: Container(key: bodyKey),
                    );
                  },
                ),
              );
            },
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(true, true));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));

    await tester.pumpWidget(buildFrame(false, true));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));

    await tester.pumpWidget(buildFrame(true, false));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));

    // This is the only case where the body is not bottom inset.
    await tester.pumpWidget(buildFrame(false, false));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0));

    await tester.pumpWidget(buildFrame(null, null));  // resizeToAvoidBottomInset default  is true
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));

    await tester.pumpWidget(buildFrame(null, false));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));

    await tester.pumpWidget(buildFrame(false, null));
    expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0));
  });
1335 1336
}

1337
class _GeometryListener extends StatefulWidget {
1338
  @override
1339
  _GeometryListenerState createState() => _GeometryListenerState();
1340 1341
}

1342
class _GeometryListenerState extends State<_GeometryListener> {
1343 1344
  @override
  Widget build(BuildContext context) {
1345
    return CustomPaint(
1346 1347 1348 1349 1350 1351
      painter: cache
    );
  }

  int numNotifications = 0;
  ValueListenable<ScaffoldGeometry> geometryListenable;
1352
  _GeometryCachePainter cache;
1353 1354 1355 1356 1357 1358 1359 1360 1361 1362

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final ValueListenable<ScaffoldGeometry> newListenable = Scaffold.geometryOf(context);
    if (geometryListenable == newListenable)
      return;

    if (geometryListenable != null)
      geometryListenable.removeListener(onGeometryChanged);
1363

1364 1365
    geometryListenable = newListenable;
    geometryListenable.addListener(onGeometryChanged);
1366
    cache = _GeometryCachePainter(geometryListenable);
1367 1368 1369 1370 1371 1372 1373 1374 1375 1376
  }

  void onGeometryChanged() {
    numNotifications += 1;
  }
}

// The Scaffold.geometryOf() value is only available at paint time.
// To fetch it for the tests we implement this CustomPainter that just
// caches the ScaffoldGeometry value in its paint method.
1377 1378
class _GeometryCachePainter extends CustomPainter {
  _GeometryCachePainter(this.geometryListenable) : super(repaint: geometryListenable);
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388

  final ValueListenable<ScaffoldGeometry> geometryListenable;

  ScaffoldGeometry value;
  @override
  void paint(Canvas canvas, Size size) {
    value = geometryListenable.value;
  }

  @override
1389
  bool shouldRepaint(_GeometryCachePainter oldDelegate) {
1390 1391
    return true;
  }
1392
}
1393

1394 1395 1396
class _CustomPageRoute<T> extends PageRoute<T> {
  _CustomPageRoute({
    @required this.builder,
1397 1398 1399
    RouteSettings settings = const RouteSettings(),
    this.maintainState = true,
    bool fullscreenDialog = false,
1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
  }) : assert(builder != null),
       super(settings: settings, fullscreenDialog: fullscreenDialog);

  final WidgetBuilder builder;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 300);

  @override
  Color get barrierColor => null;

  @override
  String get barrierLabel => null;

  @override
  final bool maintainState;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    return builder(context);
  }

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    return child;
  }
}