snack_bar_test.dart 37.5 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
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
375
    await tester.pump(const Duration(milliseconds: 750)); // Animation last frame.
376

377 378 379 380 381 382
    final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.'));
    final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.'));
    final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION'));
    final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION'));
    final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
    final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
383

384
    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
385
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0 + 40.0); // margin + bottom padding
386
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
387
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
388
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding
389
  }, skip: isBrowser);
390

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

430 431 432 433 434 435
    final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.'));
    final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.'));
    final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION'));
    final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION'));
    final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
    final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
436 437

    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
438
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0); // margin (with no bottom padding)
439 440
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
441
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding)
442
  }, skip: isBrowser);
443

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
  testWidgets('SnackBar should push FloatingActionButton above', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: MediaQuery(
        data: const MediaQueryData(
          padding: EdgeInsets.only(
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            child: const Icon(Icons.send),
            onPressed: () {}
          ),
          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(label: 'ACTION', onPressed: () {}),
                  ));
                },
                child: const Text('X'),
              );
            }
          ),
        ),
      ),
    ));

    final Offset floatingActionButtonOriginBottomCenter = tester.getCenter(find.byType(FloatingActionButton));

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

    final Offset snackBarTopCenter = tester.getCenter(find.byType(SnackBar));
    final Offset floatingActionButtonBottomCenter = tester.getCenter(find.byType(FloatingActionButton));

    expect(floatingActionButtonOriginBottomCenter.dy > floatingActionButtonBottomCenter.dy, true);
    expect(snackBarTopCenter.dy > floatingActionButtonBottomCenter.dy, true);
  });

  testWidgets('Floating SnackBar button text alignment', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(
        snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating,)
      ),
      home: MediaQuery(
        data: const MediaQueryData(
          padding: EdgeInsets.only(
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
        child: 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(label: 'ACTION', onPressed: () {}),
                  ));
                },
                child: const Text('X'),
              );
            }
          ),
        ),
      ),
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750)); // Animation last frame.

    final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.'));
    final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.'));
    final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION'));
    final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION'));
    final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
    final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));

    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 31.0 + 10.0); // margin + left padding
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 27.0); // margin (with no bottom padding)
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0);
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0); // margin + right padding
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 27.0); // margin (with no bottom padding)
539
  }, skip: isBrowser);
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594

  testWidgets('Floating SnackBar is positioned above BottomNavigationBar', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(
        snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating,)
      ),
      home: MediaQuery(
        data: const MediaQueryData(
          padding: EdgeInsets.only(
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
        child: Scaffold(
          bottomNavigationBar: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(icon: Icon(Icons.favorite), title: Text('Animutation')),
              BottomNavigationBarItem(icon: Icon(Icons.block), title: Text('Zombo.com')),
            ],
          ),
          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(label: 'ACTION', onPressed: () {}),
                  ));
                },
                child: const Text('X'),
              );
            }
          ),
        ),
      ),
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750)); // Animation last frame.

    final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.'));
    final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.'));
    final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION'));
    final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION'));
    final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
    final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));

    expect(textBottomLeft.dx - snackBarBottomLeft.dx, 31.0 + 10.0); // margin + left padding
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 27.0); // margin (with no bottom padding)
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0);
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0); // margin + right padding
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 27.0); // margin (with no bottom padding)
595
  }, skip: isBrowser);
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 632 633 634 635 636 637 638 639 640 641 642 643 644

  testWidgets('Floating SnackBar is positioned above FloatingActionButton', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(
        snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating,)
      ),
      home: MediaQuery(
        data: const MediaQueryData(
          padding: EdgeInsets.only(
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.send),
              onPressed: () {}
          ),
          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(label: 'ACTION', onPressed: () {}),
                  ));
                },
                child: const Text('X'),
              );
            }
          ),
        ),
      ),
    ));
    await tester.tap(find.text('X'));
    await tester.pump(); // start animation
    await tester.pump(const Duration(milliseconds: 750)); // Animation last frame.

    final Offset snackBarBottomCenter = tester.getBottomLeft(find.byType(SnackBar));
    final Offset floatingActionButtonTopCenter = tester.getTopLeft(find.byType(FloatingActionButton));

    // Since padding and margin is handled inside snackBarBox,
    // the bottom offset of snackbar should equal with top offset of FAB
    expect(snackBarBottomCenter.dy == floatingActionButtonTopCenter.dy, true);
  });

645
  testWidgets('SnackBarClosedReason', (WidgetTester tester) async {
646
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
647 648 649
    bool actionPressed = false;
    SnackBarClosedReason closedReason;

650 651
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
652
        key: scaffoldKey,
653
        body: Builder(
654
          builder: (BuildContext context) {
655
            return GestureDetector(
656
              onTap: () {
657
                Scaffold.of(context).showSnackBar(SnackBar(
658
                  content: const Text('snack'),
659
                  duration: const Duration(seconds: 2),
660
                  action: SnackBarAction(
661 662 663
                    label: 'ACTION',
                    onPressed: () {
                      actionPressed = true;
664
                    },
665
                  ),
666
                )).closed.then<void>((SnackBarClosedReason reason) {
667 668 669
                  closedReason = reason;
                });
              },
670
              child: const Text('X'),
671 672
            );
          },
673 674
        ),
      ),
675 676 677 678 679 680 681 682 683
    ));

    // 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);
684
    // Closed reason is only set when the animation is complete.
685
    await tester.pump(const Duration(milliseconds: 250));
686 687 688
    expect(closedReason, isNull);
    // Wait for animation to complete.
    await tester.pumpAndSettle(const Duration(seconds: 1));
689 690 691 692 693 694
    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));
695
    await tester.drag(find.text('snack'), const Offset(0.0, 50.0));
696
    await tester.pumpAndSettle(const Duration(seconds: 1));
697 698 699 700 701 702
    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();
703
    await tester.pumpAndSettle(const Duration(seconds: 1));
704 705 706 707 708 709
    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();
710
    await tester.pumpAndSettle(const Duration(seconds: 1));
711 712 713 714
    expect(closedReason, equals(SnackBarClosedReason.hide));

    // Pop up the snack bar and then let it time out.
    await tester.tap(find.text('X'));
715 716 717
    await tester.pump(const Duration(milliseconds: 750));
    await tester.pump(const Duration(milliseconds: 750));
    await tester.pump(const Duration(milliseconds: 1500));
718
    await tester.pump(); // begin animation
719
    await tester.pumpAndSettle(const Duration(seconds: 1));
720 721 722
    expect(closedReason, equals(SnackBarClosedReason.timeout));
  });

723
  testWidgets('accessible navigation behavior with action', (WidgetTester tester) async {
724
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
725

726 727 728 729 730 731 732 733 734 735 736 737 738 739
    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',
740
                      onPressed: () { },
741 742 743 744 745 746
                    ),
                  ));
                },
                child: const Text('X'),
              );
            },
747 748
          ),
        ),
749 750 751 752 753 754 755 756 757 758 759 760 761
      ),
    ));
    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);
762 763 764 765
  });

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

768 769
    await tester.pumpWidget(MaterialApp(
        home: MediaQuery(
770 771 772
            data: const MediaQueryData(accessibleNavigation: true),
            child: Scaffold(
                key: scaffoldKey,
773
                body: Builder(
774
                  builder: (BuildContext context) {
775
                    return GestureDetector(
776
                        onTap: () {
777
                          Scaffold.of(context).showSnackBar(SnackBar(
778 779
                            content: const Text('snack'),
                            duration: const Duration(seconds: 1),
780
                            action: SnackBarAction(
781
                                label: 'ACTION',
782
                                onPressed: () { },
783 784 785
                            ),
                          ));
                        },
786
                        child: const Text('X'),
787 788
                    );
                  },
789 790 791
                ),
            ),
        ),
792 793 794 795
    ));
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();

796
    expect(tester.getSemantics(find.text('snack')), matchesSemantics(
797 798 799 800 801 802 803 804 805 806
      isLiveRegion: true,
      hasDismissAction: true,
      hasScrollDownAction: true,
      hasScrollUpAction: true,
      label: 'snack',
      textDirection: TextDirection.ltr,
    ));
    handle.dispose();
  });

jslavitz's avatar
jslavitz committed
807 808
  testWidgets('SnackBar default display duration test', (WidgetTester tester) async {
    const String helloSnackBar = 'Hello SnackBar';
809
    const Key tapTarget = Key('tap-target');
810 811 812
    await tester.pumpWidget(MaterialApp(
        home: Scaffold(
            body: Builder(
jslavitz's avatar
jslavitz committed
813
                builder: (BuildContext context) {
814
                  return GestureDetector(
jslavitz's avatar
jslavitz committed
815 816
                      onTap: () {
                        Scaffold.of(context).showSnackBar(const SnackBar(
817
                            content: Text(helloSnackBar),
jslavitz's avatar
jslavitz committed
818 819 820
                        ));
                      },
                      behavior: HitTestBehavior.opaque,
821
                      child: Container(
jslavitz's avatar
jslavitz committed
822 823
                          height: 100.0,
                          width: 100.0,
824 825
                          key: tapTarget,
                      ),
jslavitz's avatar
jslavitz committed
826 827
                  );
                }
828 829
            ),
        ),
jslavitz's avatar
jslavitz committed
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
    ));
    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);
  });

855
  testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
856
    Future<void> boilerplate({ bool accessibleNavigation }) {
857 858 859 860 861
      return tester.pumpWidget(MaterialApp(
          home: MediaQuery(
              data: MediaQueryData(accessibleNavigation: accessibleNavigation),
              child: Scaffold(
                  body: Builder(
862
                      builder: (BuildContext context) {
863
                        return GestureDetector(
864
                            onTap: () {
865
                              Scaffold.of(context).showSnackBar(SnackBar(
866
                                  content: const Text('test'),
867
                                  action: SnackBarAction(label: 'foo', onPressed: () { }),
868 869 870 871 872 873
                              ));
                            },
                            behavior: HitTestBehavior.opaque,
                            child: const Text('X'),
                        );
                      }
874 875 876
                  ),
              ),
          ),
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
      ));
    }

    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);
  });

903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
  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,
920
                key: tapTarget,
921 922 923 924 925 926 927 928 929 930
              ),
            );
          },
        ),
      ),
    ));

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