snack_bar_test.dart 30.1 KB
Newer Older
Hixie's avatar
Hixie committed
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.

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

void main() {
9
  testWidgets('SnackBar control test', (WidgetTester tester) async {
10
    const String helloSnackBar = 'Hello SnackBar';
11
    const Key tapTarget = Key('tap-target');
12 13 14
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
15
          builder: (BuildContext context) {
16
            return GestureDetector(
17
              onTap: () {
18
                Scaffold.of(context).showSnackBar(const SnackBar(
19
                  content: Text(helloSnackBar),
20
                  duration: Duration(seconds: 2),
21 22 23
                ));
              },
              behavior: HitTestBehavior.opaque,
24
              child: Container(
25 26
                height: 100.0,
                width: 100.0,
27 28
                key: tapTarget,
              ),
29 30
            );
          }
31 32
        ),
      ),
33 34
    ));
    expect(find.text(helloSnackBar), findsNothing);
35
    await tester.tap(find.byKey(tapTarget));
36
    expect(find.text(helloSnackBar), findsNothing);
37
    await tester.pump(); // schedule animation
38
    expect(find.text(helloSnackBar), findsOneWidget);
39
    await tester.pump(); // begin animation
40
    expect(find.text(helloSnackBar), findsOneWidget);
41
    await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
42
    expect(find.text(helloSnackBar), findsOneWidget);
43
    await tester.pump(const Duration(milliseconds: 750)); // 1.50s
44
    expect(find.text(helloSnackBar), findsOneWidget);
45
    await tester.pump(const Duration(milliseconds: 750)); // 2.25s
46
    expect(find.text(helloSnackBar), findsOneWidget);
47
    await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
48
    await tester.pump(); // begin animation
49
    expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
50
    await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build
51
    expect(find.text(helloSnackBar), findsNothing);
Hixie's avatar
Hixie committed
52 53
  });

54
  testWidgets('SnackBar twice test', (WidgetTester tester) async {
55
    int snackBarCount = 0;
56
    const Key tapTarget = Key('tap-target');
57 58 59
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
60
          builder: (BuildContext context) {
61
            return GestureDetector(
62 63
              onTap: () {
                snackBarCount += 1;
64 65
                Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text('bar$snackBarCount'),
66
                  duration: const Duration(seconds: 2),
67 68 69
                ));
              },
              behavior: HitTestBehavior.opaque,
70
              child: Container(
71 72
                height: 100.0,
                width: 100.0,
73 74
                key: tapTarget,
              ),
75 76
            );
          }
77 78
        ),
      ),
79 80 81
    ));
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
82 83
    await tester.tap(find.byKey(tapTarget)); // queue bar1
    await tester.tap(find.byKey(tapTarget)); // queue bar2
84 85
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
86
    await tester.pump(); // schedule animation for bar1
87 88
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
89
    await tester.pump(); // begin animation
90 91
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
92
    await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
93 94
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
95
    await tester.pump(const Duration(milliseconds: 750)); // 1.50s
96 97
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
98
    await tester.pump(const Duration(milliseconds: 750)); // 2.25s
99 100
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
101
    await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
102
    await tester.pump(); // begin animation
103 104
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
105
    await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
106 107
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
108
    await tester.pump(); // begin animation
109 110
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
111
    await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here
112 113
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
114
    await tester.pump(const Duration(milliseconds: 750)); // 5.25s
115 116
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
117
    await tester.pump(const Duration(milliseconds: 750)); // 6.00s
118 119
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
120
    await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
121
    await tester.pump(); // begin animation
122 123
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
124
    await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
125 126
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
127
  });
128

129
  testWidgets('SnackBar cancel test', (WidgetTester tester) async {
130
    int snackBarCount = 0;
131
    const Key tapTarget = Key('tap-target');
132
    int time;
133
    ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController;
134 135 136
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
137
          builder: (BuildContext context) {
138
            return GestureDetector(
139 140
              onTap: () {
                snackBarCount += 1;
141 142
                lastController = Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text('bar$snackBarCount'),
143
                  duration: Duration(seconds: time),
144 145 146
                ));
              },
              behavior: HitTestBehavior.opaque,
147
              child: Container(
148 149
                height: 100.0,
                width: 100.0,
150 151
                key: tapTarget,
              ),
152 153
            );
          }
154 155
        ),
      ),
156 157 158 159
    ));
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
    time = 1000;
160
    await tester.tap(find.byKey(tapTarget)); // queue bar1
161
    final ScaffoldFeatureController<SnackBar, SnackBarClosedReason> firstController = lastController;
162
    time = 2;
163
    await tester.tap(find.byKey(tapTarget)); // queue bar2
164 165
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
166
    await tester.pump(); // schedule animation for bar1
167 168
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
169
    await tester.pump(); // begin animation
170 171
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
172
    await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
173 174
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
175
    await tester.pump(const Duration(milliseconds: 750)); // 1.50s
176 177
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
178
    await tester.pump(const Duration(milliseconds: 750)); // 2.25s
179 180
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
181
    await tester.pump(const Duration(milliseconds: 10000)); // 12.25s
182 183
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
184

185
    firstController.close(); // snackbar is manually dismissed
186

187
    await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled
188
    await tester.pump(); // begin animation
189 190
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
191
    await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
192 193
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
194
    await tester.pump(); // begin animation
195 196
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
197
    await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here
198 199
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
200
    await tester.pump(const Duration(milliseconds: 750)); // 15.25s
201 202
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
203
    await tester.pump(const Duration(milliseconds: 750)); // 16.00s
204 205
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
206
    await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
207
    await tester.pump(); // begin animation
208 209
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
210
    await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
211 212
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
213
  });
214

215
  testWidgets('SnackBar dismiss test', (WidgetTester tester) async {
216
    int snackBarCount = 0;
217
    const Key tapTarget = Key('tap-target');
218 219 220
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
221
          builder: (BuildContext context) {
222
            return GestureDetector(
223 224
              onTap: () {
                snackBarCount += 1;
225 226
                Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text('bar$snackBarCount'),
227
                  duration: const Duration(seconds: 2),
228 229 230
                ));
              },
              behavior: HitTestBehavior.opaque,
231
              child: Container(
232 233
                height: 100.0,
                width: 100.0,
234 235
                key: tapTarget,
              ),
236 237
            );
          }
238 239
        ),
      ),
240 241 242
    ));
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
243 244
    await tester.tap(find.byKey(tapTarget)); // queue bar1
    await tester.tap(find.byKey(tapTarget)); // queue bar2
245 246
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsNothing);
247
    await tester.pump(); // schedule animation for bar1
248 249
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
250
    await tester.pump(); // begin animation
251 252
    expect(find.text('bar1'), findsOneWidget);
    expect(find.text('bar2'), findsNothing);
253
    await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
254
    await tester.drag(find.text('bar1'), const Offset(0.0, 50.0));
255
    await tester.pump(); // bar1 dismissed, bar2 begins animating
256 257
    expect(find.text('bar1'), findsNothing);
    expect(find.text('bar2'), findsOneWidget);
258 259
  });

260
  testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async {
261
    int tapCount = 0;
262 263 264
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
265
          builder: (BuildContext context) {
266
            return GestureDetector(
267
              onTap: () {
268
                Scaffold.of(context).showSnackBar(SnackBar(
269
                  content: const Text('I am a snack bar.'),
270
                  duration: const Duration(seconds: 2),
271
                  action: SnackBarAction(
272 273 274
                    label: 'ACTION',
                    onPressed: () {
                      ++tapCount;
275 276
                    },
                  ),
277 278
                ));
              },
279
              child: const Text('X'),
280 281
            );
          }
282 283
        ),
      ),
284
    ));
285 286 287
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750));
288

289
    expect(tapCount, equals(0));
290
    await tester.tap(find.text('ACTION'));
291
    expect(tapCount, equals(1));
292
    await tester.tap(find.text('ACTION'));
293
    expect(tapCount, equals(1));
294 295
    await tester.pump();
    await tester.tap(find.text('ACTION'));
296
    expect(tapCount, equals(1));
297
  });
298

jslavitz's avatar
jslavitz committed
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  testWidgets('Snackbar labels can be colored', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (BuildContext context) {
              return GestureDetector(
                onTap: () {
                  Scaffold.of(context).showSnackBar(
                    SnackBar(
                      content: const Text('I am a snack bar.'),
                      duration: const Duration(seconds: 2),
                      action: SnackBarAction(
                        textColor: Colors.lightBlue,
                        disabledTextColor: Colors.red,
                        label: 'ACTION',
315
                        onPressed: () { },
jslavitz's avatar
jslavitz committed
316 317 318 319
                      ),
                    ),
                  );
                },
320
                child: const Text('X'),
jslavitz's avatar
jslavitz committed
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
              );
            }
          ),
        ),
      ),
    );

    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750));

    final Element actionTextBox = tester.element(find.text('ACTION'));
    final Widget textWidget = actionTextBox.widget;
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(actionTextBox);
    if (textWidget is Text) {
      TextStyle effectiveStyle = textWidget.style;
      effectiveStyle = defaultTextStyle.style.merge(textWidget.style);
      expect(effectiveStyle.color, Colors.lightBlue);
339
    } else {
jslavitz's avatar
jslavitz committed
340
      expect(false, true);
341
    }
jslavitz's avatar
jslavitz committed
342 343
  });

344
  testWidgets('SnackBar button text alignment', (WidgetTester tester) async {
345 346
    await tester.pumpWidget(MaterialApp(
      home: MediaQuery(
347
        data: const MediaQueryData(
348
          padding: EdgeInsets.only(
349 350 351 352 353 354
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
355 356
        child: Scaffold(
          body: Builder(
357
            builder: (BuildContext context) {
358
              return GestureDetector(
359
                onTap: () {
360
                  Scaffold.of(context).showSnackBar(SnackBar(
361 362
                    content: const Text('I am a snack bar.'),
                    duration: const Duration(seconds: 2),
363
                    action: SnackBarAction(label: 'ACTION', onPressed: () { }),
364 365
                  ));
                },
366
                child: const Text('X'),
367 368 369 370 371
              );
            }
          ),
        ),
      ),
372 373 374 375 376
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750));

377 378 379
    final RenderBox textBox = tester.firstRenderObject(find.text('I am a snack bar.'));
    final RenderBox actionTextBox = tester.firstRenderObject(find.text('ACTION'));
    final RenderBox snackBarBox = tester.firstRenderObject(find.byType(SnackBar));
380

381 382 383 384 385 386
    final Offset textBottomLeft = textBox.localToGlobal(textBox.size.bottomLeft(Offset.zero));
    final Offset textBottomRight = textBox.localToGlobal(textBox.size.bottomRight(Offset.zero));
    final Offset actionTextBottomLeft = actionTextBox.localToGlobal(actionTextBox.size.bottomLeft(Offset.zero));
    final Offset actionTextBottomRight = actionTextBox.localToGlobal(actionTextBox.size.bottomRight(Offset.zero));
    final Offset snackBarBottomLeft = snackBarBox.localToGlobal(snackBarBox.size.bottomLeft(Offset.zero));
    final Offset snackBarBottomRight = snackBarBox.localToGlobal(snackBarBox.size.bottomRight(Offset.zero));
387

388
    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
389
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0 + 40.0); // margin + bottom padding
390
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
391
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
392
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding
393
  });
394

395
  testWidgets('SnackBar is positioned above BottomNavigationBar', (WidgetTester tester) async {
396 397
    await tester.pumpWidget(MaterialApp(
      home: MediaQuery(
398
        data: const MediaQueryData(
399
          padding: EdgeInsets.only(
400 401 402 403 404 405
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
406 407
        child: Scaffold(
          bottomNavigationBar: BottomNavigationBar(
408
            items: const <BottomNavigationBarItem>[
409 410
              BottomNavigationBarItem(icon: Icon(Icons.favorite), title: Text('Animutation')),
              BottomNavigationBarItem(icon: Icon(Icons.block), title: Text('Zombo.com')),
411 412
            ],
          ),
413
          body: Builder(
414
            builder: (BuildContext context) {
415
              return GestureDetector(
416
                onTap: () {
417
                  Scaffold.of(context).showSnackBar(SnackBar(
418 419
                    content: const Text('I am a snack bar.'),
                    duration: const Duration(seconds: 2),
420
                    action: SnackBarAction(label: 'ACTION', onPressed: () { }),
421 422
                  ));
                },
423
                child: const Text('X'),
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
              );
            }
          ),
        ),
      ),
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750));

    final RenderBox textBox = tester.firstRenderObject(find.text('I am a snack bar.'));
    final RenderBox actionTextBox = tester.firstRenderObject(find.text('ACTION'));
    final RenderBox snackBarBox = tester.firstRenderObject(find.byType(SnackBar));

    final Offset textBottomLeft = textBox.localToGlobal(textBox.size.bottomLeft(Offset.zero));
    final Offset textBottomRight = textBox.localToGlobal(textBox.size.bottomRight(Offset.zero));
    final Offset actionTextBottomLeft = actionTextBox.localToGlobal(actionTextBox.size.bottomLeft(Offset.zero));
    final Offset actionTextBottomRight = actionTextBox.localToGlobal(actionTextBox.size.bottomRight(Offset.zero));
    final Offset snackBarBottomLeft = snackBarBox.localToGlobal(snackBarBox.size.bottomLeft(Offset.zero));
    final Offset snackBarBottomRight = snackBarBox.localToGlobal(snackBarBox.size.bottomRight(Offset.zero));

    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
446
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0); // margin (with no bottom padding)
447 448
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
449
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding)
450 451
  });

452
  testWidgets('SnackBarClosedReason', (WidgetTester tester) async {
453
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
454 455 456
    bool actionPressed = false;
    SnackBarClosedReason closedReason;

457 458
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
459
        key: scaffoldKey,
460
        body: Builder(
461
          builder: (BuildContext context) {
462
            return GestureDetector(
463
              onTap: () {
464
                Scaffold.of(context).showSnackBar(SnackBar(
465
                  content: const Text('snack'),
466
                  duration: const Duration(seconds: 2),
467
                  action: SnackBarAction(
468 469 470
                    label: 'ACTION',
                    onPressed: () {
                      actionPressed = true;
471
                    },
472
                  ),
473
                )).closed.then<void>((SnackBarClosedReason reason) {
474 475 476
                  closedReason = reason;
                });
              },
477
              child: const Text('X'),
478 479
            );
          },
480 481
        ),
      ),
482 483 484 485 486 487 488 489 490
    ));

    // Pop up the snack bar and then press its action button.
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750));
    expect(actionPressed, isFalse);
    await tester.tap(find.text('ACTION'));
    expect(actionPressed, isTrue);
491
    // Closed reason is only set when the animation is complete.
492
    await tester.pump(const Duration(milliseconds: 250));
493 494 495
    expect(closedReason, isNull);
    // Wait for animation to complete.
    await tester.pumpAndSettle(const Duration(seconds: 1));
496 497 498 499 500 501
    expect(closedReason, equals(SnackBarClosedReason.action));

    // Pop up the snack bar and then swipe downwards to dismiss it.
    await tester.tap(find.text('X'));
    await tester.pump(const Duration(milliseconds: 750));
    await tester.pump(const Duration(milliseconds: 750));
502
    await tester.drag(find.text('snack'), const Offset(0.0, 50.0));
503
    await tester.pumpAndSettle(const Duration(seconds: 1));
504 505 506 507 508 509
    expect(closedReason, equals(SnackBarClosedReason.swipe));

    // Pop up the snack bar and then remove it.
    await tester.tap(find.text('X'));
    await tester.pump(const Duration(milliseconds: 750));
    scaffoldKey.currentState.removeCurrentSnackBar();
510
    await tester.pumpAndSettle(const Duration(seconds: 1));
511 512 513 514 515 516
    expect(closedReason, equals(SnackBarClosedReason.remove));

    // Pop up the snack bar and then hide it.
    await tester.tap(find.text('X'));
    await tester.pump(const Duration(milliseconds: 750));
    scaffoldKey.currentState.hideCurrentSnackBar();
517
    await tester.pumpAndSettle(const Duration(seconds: 1));
518 519 520 521
    expect(closedReason, equals(SnackBarClosedReason.hide));

    // Pop up the snack bar and then let it time out.
    await tester.tap(find.text('X'));
522 523 524
    await tester.pump(const Duration(milliseconds: 750));
    await tester.pump(const Duration(milliseconds: 750));
    await tester.pump(const Duration(milliseconds: 1500));
525
    await tester.pump(); // begin animation
526
    await tester.pumpAndSettle(const Duration(seconds: 1));
527 528 529
    expect(closedReason, equals(SnackBarClosedReason.timeout));
  });

530
  testWidgets('accessible navigation behavior with action', (WidgetTester tester) async {
531
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
532

533 534 535 536 537 538 539 540 541 542 543 544 545 546
    await tester.pumpWidget(MaterialApp(
      home: MediaQuery(
        data: const MediaQueryData(accessibleNavigation: true),
        child: Scaffold(
          key: scaffoldKey,
          body: Builder(
            builder: (BuildContext context) {
              return GestureDetector(
                onTap: () {
                  Scaffold.of(context).showSnackBar(SnackBar(
                    content: const Text('snack'),
                    duration: const Duration(seconds: 1),
                    action: SnackBarAction(
                      label: 'ACTION',
547
                      onPressed: () { },
548 549 550 551 552 553
                    ),
                  ));
                },
                child: const Text('X'),
              );
            },
554 555
          ),
        ),
556 557 558 559 560 561 562 563 564 565 566 567 568
      ),
    ));
    await tester.tap(find.text('X'));
    await tester.pump();
    // Find action immediately
    expect(find.text('ACTION'), findsOneWidget);
    // Snackbar doesn't close
    await tester.pump(const Duration(seconds: 10));
    expect(find.text('ACTION'), findsOneWidget);
    await tester.tap(find.text('ACTION'));
    await tester.pump();
    // Snackbar closes immediately
    expect(find.text('ACTION'), findsNothing);
569 570 571 572
  });

  testWidgets('contributes dismiss semantics', (WidgetTester tester) async {
    final SemanticsHandle handle = tester.ensureSemantics();
573
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
574

575 576
    await tester.pumpWidget(MaterialApp(
        home: MediaQuery(
577 578 579
            data: const MediaQueryData(accessibleNavigation: true),
            child: Scaffold(
                key: scaffoldKey,
580
                body: Builder(
581
                  builder: (BuildContext context) {
582
                    return GestureDetector(
583
                        onTap: () {
584
                          Scaffold.of(context).showSnackBar(SnackBar(
585 586
                            content: const Text('snack'),
                            duration: const Duration(seconds: 1),
587
                            action: SnackBarAction(
588
                                label: 'ACTION',
589
                                onPressed: () { },
590 591 592
                            ),
                          ));
                        },
593
                        child: const Text('X'),
594 595
                    );
                  },
596 597 598
                ),
            ),
        ),
599 600 601 602
    ));
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();

603
    expect(tester.getSemantics(find.text('snack')), matchesSemantics(
604 605 606 607 608 609 610 611 612 613
      isLiveRegion: true,
      hasDismissAction: true,
      hasScrollDownAction: true,
      hasScrollUpAction: true,
      label: 'snack',
      textDirection: TextDirection.ltr,
    ));
    handle.dispose();
  });

jslavitz's avatar
jslavitz committed
614 615
  testWidgets('SnackBar default display duration test', (WidgetTester tester) async {
    const String helloSnackBar = 'Hello SnackBar';
616
    const Key tapTarget = Key('tap-target');
617 618 619
    await tester.pumpWidget(MaterialApp(
        home: Scaffold(
            body: Builder(
jslavitz's avatar
jslavitz committed
620
                builder: (BuildContext context) {
621
                  return GestureDetector(
jslavitz's avatar
jslavitz committed
622 623
                      onTap: () {
                        Scaffold.of(context).showSnackBar(const SnackBar(
624
                            content: Text(helloSnackBar),
jslavitz's avatar
jslavitz committed
625 626 627
                        ));
                      },
                      behavior: HitTestBehavior.opaque,
628
                      child: Container(
jslavitz's avatar
jslavitz committed
629 630
                          height: 100.0,
                          width: 100.0,
631 632
                          key: tapTarget,
                      ),
jslavitz's avatar
jslavitz committed
633 634
                  );
                }
635 636
            ),
        ),
jslavitz's avatar
jslavitz committed
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    ));
    expect(find.text(helloSnackBar), findsNothing);
    await tester.tap(find.byKey(tapTarget));
    expect(find.text(helloSnackBar), findsNothing);
    await tester.pump(); // schedule animation
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(); // begin animation
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; four second timer starts here
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 750)); // 1.50s
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 750)); // 2.25s
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 750)); // 3.00s
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 750)); // 3.75s
    expect(find.text(helloSnackBar), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 1000)); // 4.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
    await tester.pump(); // begin animation
    expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
    await tester.pump(const Duration(milliseconds: 750)); // 5.50s // last frame of animation, snackbar removed from build
    expect(find.text(helloSnackBar), findsNothing);
  });

662
  testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
663
    Future<void> boilerplate({ bool accessibleNavigation }) {
664 665 666 667 668
      return tester.pumpWidget(MaterialApp(
          home: MediaQuery(
              data: MediaQueryData(accessibleNavigation: accessibleNavigation),
              child: Scaffold(
                  body: Builder(
669
                      builder: (BuildContext context) {
670
                        return GestureDetector(
671
                            onTap: () {
672
                              Scaffold.of(context).showSnackBar(SnackBar(
673
                                  content: const Text('test'),
674
                                  action: SnackBarAction(label: 'foo', onPressed: () { }),
675 676 677 678 679 680
                              ));
                            },
                            behavior: HitTestBehavior.opaque,
                            child: const Text('X'),
                        );
                      }
681 682 683
                  ),
              ),
          ),
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
      ));
    }

    await boilerplate(accessibleNavigation: false);
    expect(find.text('test'), findsNothing);
    await tester.tap(find.text('X'));
    await tester.pump(); // schedule animation
    expect(find.text('test'), findsOneWidget);
    await tester.pump(); // begin animation
    await tester.pump(const Duration(milliseconds: 4750)); // 4.75s
    expect(find.text('test'), findsOneWidget);

    // Enabled accessible navigation
    await boilerplate(accessibleNavigation: true);

    await tester.pump(const Duration(milliseconds: 4000)); // 8.75s
    await tester.pump();
    expect(find.text('test'), findsOneWidget);

    // disable accessible navigation
    await boilerplate(accessibleNavigation: false);
    await tester.pumpAndSettle(const Duration(milliseconds: 5750));

    expect(find.text('test'), findsNothing);
  });

710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
  testWidgets('Snackbar asserts if passed a null duration', (WidgetTester tester) async {
    const Key tapTarget = Key('tap-target');
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            return GestureDetector(
              onTap: () {
                Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text(nonconst('hello')),
                  duration: null,
                ));
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 100.0,
                width: 100.0,
727
                key: tapTarget,
728 729 730 731 732 733 734 735 736 737
              ),
            );
          },
        ),
      ),
    ));

    await tester.tap(find.byKey(tapTarget));
    expect(tester.takeException(), isNotNull);
  });
738
}