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 20
                  content: Text(helloSnackBar),
                  duration: Duration(seconds: 2)
21 22 23
                ));
              },
              behavior: HitTestBehavior.opaque,
24
              child: Container(
25 26 27 28 29 30
                height: 100.0,
                width: 100.0,
                key: tapTarget
              )
            );
          }
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 73 74 75 76
                height: 100.0,
                width: 100.0,
                key: tapTarget
              )
            );
          }
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 143
                lastController = Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text('bar$snackBarCount'),
                  duration: Duration(seconds: time)
144 145 146
                ));
              },
              behavior: HitTestBehavior.opaque,
147
              child: Container(
148 149 150 151 152 153
                height: 100.0,
                width: 100.0,
                key: tapTarget
              )
            );
          }
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 234 235 236 237
                height: 100.0,
                width: 100.0,
                key: tapTarget
              )
            );
          }
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 275 276 277 278
                    label: 'ACTION',
                    onPressed: () {
                      ++tapCount;
                    }
                  )
                ));
              },
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 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
  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',
                        onPressed: () {},
                      ),
                    ),
                  );
                },
                child: const Text('X')
              );
            }
          ),
        ),
      ),
    );

    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);
    } else
      expect(false, true);
  });

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

376 377 378
    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));
379

380 381 382 383 384 385
    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));
386

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

394
  testWidgets('SnackBar is positioned above BottomNavigationBar', (WidgetTester tester) async {
395 396
    await tester.pumpWidget(MaterialApp(
      home: MediaQuery(
397
        data: const MediaQueryData(
398
          padding: EdgeInsets.only(
399 400 401 402 403 404
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
405 406
        child: Scaffold(
          bottomNavigationBar: BottomNavigationBar(
407
            items: const <BottomNavigationBarItem>[
408 409
              BottomNavigationBarItem(icon: Icon(Icons.favorite), title: Text('Animutation')),
              BottomNavigationBarItem(icon: Icon(Icons.block), title: Text('Zombo.com')),
410 411
            ],
          ),
412
          body: Builder(
413
            builder: (BuildContext context) {
414
              return GestureDetector(
415
                onTap: () {
416
                  Scaffold.of(context).showSnackBar(SnackBar(
417 418
                    content: const Text('I am a snack bar.'),
                    duration: const Duration(seconds: 2),
419
                    action: SnackBarAction(label: 'ACTION', onPressed: () {})
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
                  ));
                },
                child: const Text('X')
              );
            }
          ),
        ),
      ),
    ));
    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
445
    expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0); // margin (with no bottom padding)
446 447
    expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
    expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
448
    expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding)
449 450
  });

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

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

    // 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);
490
    // Closed reason is only set when the animation is complete.
491
    await tester.pump(const Duration(milliseconds: 250));
492 493 494
    expect(closedReason, isNull);
    // Wait for animation to complete.
    await tester.pumpAndSettle(const Duration(seconds: 1));
495 496 497 498 499 500
    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));
501
    await tester.drag(find.text('snack'), const Offset(0.0, 50.0));
502
    await tester.pumpAndSettle(const Duration(seconds: 1));
503 504 505 506 507 508
    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();
509
    await tester.pumpAndSettle(const Duration(seconds: 1));
510 511 512 513 514 515
    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();
516
    await tester.pumpAndSettle(const Duration(seconds: 1));
517 518 519 520
    expect(closedReason, equals(SnackBarClosedReason.hide));

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

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

532 533
      await tester.pumpWidget(MaterialApp(
        home: MediaQuery(
534 535 536
          data: const MediaQueryData(accessibleNavigation: true),
          child: Scaffold(
            key: scaffoldKey,
537
            body: Builder(
538
              builder: (BuildContext context) {
539
                return GestureDetector(
540
                  onTap: () {
541
                    Scaffold.of(context).showSnackBar(SnackBar(
542 543
                      content: const Text('snack'),
                      duration: const Duration(seconds: 1),
544
                      action: SnackBarAction(
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
                        label: 'ACTION',
                        onPressed: () {}
                      ),
                    ));
                  },
                  child: const Text('X')
                );
              },
            )
          )
        )
      ));
      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);
  });

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

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

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

jslavitz's avatar
jslavitz committed
613 614
  testWidgets('SnackBar default display duration test', (WidgetTester tester) async {
    const String helloSnackBar = 'Hello SnackBar';
615
    const Key tapTarget = Key('tap-target');
616 617 618
    await tester.pumpWidget(MaterialApp(
        home: Scaffold(
            body: Builder(
jslavitz's avatar
jslavitz committed
619
                builder: (BuildContext context) {
620
                  return GestureDetector(
jslavitz's avatar
jslavitz committed
621 622
                      onTap: () {
                        Scaffold.of(context).showSnackBar(const SnackBar(
623
                            content: Text(helloSnackBar)
jslavitz's avatar
jslavitz committed
624 625 626
                        ));
                      },
                      behavior: HitTestBehavior.opaque,
627
                      child: Container(
jslavitz's avatar
jslavitz committed
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
                          height: 100.0,
                          width: 100.0,
                          key: tapTarget
                      )
                  );
                }
            )
        )
    ));
    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);
  });

661 662
  testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
    Future<void> boilerplate({bool accessibleNavigation}) {
663 664 665 666 667
      return tester.pumpWidget(MaterialApp(
          home: MediaQuery(
              data: MediaQueryData(accessibleNavigation: accessibleNavigation),
              child: Scaffold(
                  body: Builder(
668
                      builder: (BuildContext context) {
669
                        return GestureDetector(
670
                            onTap: () {
671
                              Scaffold.of(context).showSnackBar(SnackBar(
672
                                  content: const Text('test'),
673
                                  action: SnackBarAction(label: 'foo', onPressed: () {}),
674 675 676 677 678 679 680 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
                              ));
                            },
                            behavior: HitTestBehavior.opaque,
                            child: const Text('X'),
                        );
                      }
                  )
              )
          )
      ));
    }

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

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
  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,
                key: tapTarget
              ),
            );
          },
        ),
      ),
    ));

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