// 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. import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('SnackBar control test', (WidgetTester tester) async { const String helloSnackBar = 'Hello SnackBar'; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(const SnackBar( content: Text(helloSnackBar), duration: Duration(seconds: 2), )); }, behavior: HitTestBehavior.opaque, child: Container( 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; two 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 // 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)); // 3.75s // last frame of animation, snackbar removed from build expect(find.text(helloSnackBar), findsNothing); }); testWidgets('SnackBar twice test', (WidgetTester tester) async { int snackBarCount = 0; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { snackBarCount += 1; Scaffold.of(context).showSnackBar(SnackBar( content: Text('bar$snackBarCount'), duration: const Duration(seconds: 2), )); }, behavior: HitTestBehavior.opaque, child: Container( height: 100.0, width: 100.0, key: tapTarget, ), ); } ), ), )); expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); await tester.tap(find.byKey(tapTarget)); // queue bar1 await tester.tap(find.byKey(tapTarget)); // queue bar2 expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); await tester.pump(); // schedule animation for bar1 expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(); // begin animation expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 1.50s expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 2.25s expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled await tester.pump(); // begin animation expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(); // begin animation expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 5.25s expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 6.00s expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled await tester.pump(); // begin animation expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); }); testWidgets('SnackBar cancel test', (WidgetTester tester) async { int snackBarCount = 0; const Key tapTarget = Key('tap-target'); int time; ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController; await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { snackBarCount += 1; lastController = Scaffold.of(context).showSnackBar(SnackBar( content: Text('bar$snackBarCount'), duration: Duration(seconds: time), )); }, behavior: HitTestBehavior.opaque, child: Container( height: 100.0, width: 100.0, key: tapTarget, ), ); } ), ), )); expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); time = 1000; await tester.tap(find.byKey(tapTarget)); // queue bar1 final ScaffoldFeatureController<SnackBar, SnackBarClosedReason> firstController = lastController; time = 2; await tester.tap(find.byKey(tapTarget)); // queue bar2 expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); await tester.pump(); // schedule animation for bar1 expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(); // begin animation expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 1.50s expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 2.25s expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 10000)); // 12.25s expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); firstController.close(); // snackbar is manually dismissed await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled await tester.pump(); // begin animation expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(); // begin animation expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 15.25s expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 16.00s expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled await tester.pump(); // begin animation expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); }); testWidgets('SnackBar dismiss test', (WidgetTester tester) async { int snackBarCount = 0; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { snackBarCount += 1; Scaffold.of(context).showSnackBar(SnackBar( content: Text('bar$snackBarCount'), duration: const Duration(seconds: 2), )); }, behavior: HitTestBehavior.opaque, child: Container( height: 100.0, width: 100.0, key: tapTarget, ), ); } ), ), )); expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); await tester.tap(find.byKey(tapTarget)); // queue bar1 await tester.tap(find.byKey(tapTarget)); // queue bar2 expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsNothing); await tester.pump(); // schedule animation for bar1 expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(); // begin animation expect(find.text('bar1'), findsOneWidget); expect(find.text('bar2'), findsNothing); await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here await tester.drag(find.text('bar1'), const Offset(0.0, 50.0)); await tester.pump(); // bar1 dismissed, bar2 begins animating expect(find.text('bar1'), findsNothing); expect(find.text('bar2'), findsOneWidget); }); testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async { int tapCount = 0; 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( label: 'ACTION', onPressed: () { ++tapCount; }, ), )); }, child: const Text('X'), ); } ), ), )); await tester.tap(find.text('X')); await tester.pump(); // start animation await tester.pump(const Duration(milliseconds: 750)); expect(tapCount, equals(0)); await tester.tap(find.text('ACTION')); expect(tapCount, equals(1)); await tester.tap(find.text('ACTION')); expect(tapCount, equals(1)); await tester.pump(); await tester.tap(find.text('ACTION')); expect(tapCount, equals(1)); }); testWidgets('Light theme SnackBar has dark background', (WidgetTester tester) async { final ThemeData lightTheme = ThemeData.light(); await tester.pumpWidget( MaterialApp( theme: lightTheme, 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( 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 RenderPhysicalModel renderModel = tester.renderObject( find.widgetWithText(Material, 'I am a snack bar.').first ); // There is a somewhat complicated background color calculation based // off of the surface color. For the default light theme it // should be this value. expect(renderModel.color, equals(const Color(0xFF333333))); }); testWidgets('Dark theme SnackBar has light background', (WidgetTester tester) async { final ThemeData darkTheme = ThemeData.dark(); await tester.pumpWidget( MaterialApp( theme: darkTheme, 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( 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 RenderPhysicalModel renderModel = tester.renderObject( find.widgetWithText(Material, 'I am a snack bar.').first ); expect(renderModel.color, equals(darkTheme.colorScheme.onSurface)); }); 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); } }); testWidgets('SnackBar button text alignment', (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( 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, 24.0 + 10.0); // margin + left padding expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0 + 40.0); // margin + bottom padding expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding }, skip: isBrowser); testWidgets('SnackBar is positioned above BottomNavigationBar', (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( 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, 24.0 + 10.0); // margin + left padding expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0); // margin (with no bottom padding) expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0); expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding) }, skip: isBrowser); 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) }, skip: isBrowser); 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) }, skip: isBrowser); 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); }); testWidgets('SnackBar bottom padding is not consumed by viewInsets', (WidgetTester tester) async { final Widget child = Directionality( textDirection: TextDirection.ltr, child: Scaffold( resizeToAvoidBottomInset: false, 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.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only(bottom: 20.0), ), child: child, ), ); await tester.tap(find.text('X')); await tester.pumpAndSettle(); // Show snackbar final Offset initialPoint = tester.getCenter(find.byType(SnackBar)); // Consume bottom padding - as if by the keyboard opening await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.zero, viewPadding: EdgeInsets.only(bottom: 20), viewInsets: EdgeInsets.only(bottom: 300), ), child: child, ), ); await tester.tap(find.text('X')); await tester.pumpAndSettle(); final Offset finalPoint = tester.getCenter(find.byType(SnackBar)); expect(initialPoint, finalPoint); }); testWidgets('SnackBarClosedReason', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); bool actionPressed = false; SnackBarClosedReason closedReason; await tester.pumpWidget(MaterialApp( home: Scaffold( key: scaffoldKey, body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: const Text('snack'), duration: const Duration(seconds: 2), action: SnackBarAction( label: 'ACTION', onPressed: () { actionPressed = true; }, ), )).closed.then<void>((SnackBarClosedReason reason) { closedReason = reason; }); }, child: const Text('X'), ); }, ), ), )); // 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); // Closed reason is only set when the animation is complete. await tester.pump(const Duration(milliseconds: 250)); expect(closedReason, isNull); // Wait for animation to complete. await tester.pumpAndSettle(const Duration(seconds: 1)); 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)); await tester.drag(find.text('snack'), const Offset(0.0, 50.0)); await tester.pumpAndSettle(const Duration(seconds: 1)); 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(); await tester.pumpAndSettle(const Duration(seconds: 1)); 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(); await tester.pumpAndSettle(const Duration(seconds: 1)); expect(closedReason, equals(SnackBarClosedReason.hide)); // Pop up the snack bar and then let it time out. await tester.tap(find.text('X')); await tester.pump(const Duration(milliseconds: 750)); await tester.pump(const Duration(milliseconds: 750)); await tester.pump(const Duration(milliseconds: 1500)); await tester.pump(); // begin animation await tester.pumpAndSettle(const Duration(seconds: 1)); expect(closedReason, equals(SnackBarClosedReason.timeout)); }); testWidgets('accessible navigation behavior with action', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); 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', 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(); final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); 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', onPressed: () { }, ), )); }, child: const Text('X'), ); }, ), ), ), )); await tester.tap(find.text('X')); await tester.pumpAndSettle(); expect(tester.getSemantics(find.text('snack')), matchesSemantics( isLiveRegion: true, hasDismissAction: true, hasScrollDownAction: true, hasScrollUpAction: true, label: 'snack', textDirection: TextDirection.ltr, )); handle.dispose(); }); testWidgets('SnackBar default display duration test', (WidgetTester tester) async { const String helloSnackBar = 'Hello SnackBar'; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(const SnackBar( content: Text(helloSnackBar), )); }, behavior: HitTestBehavior.opaque, child: Container( 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); }); testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async { Future<void> boilerplate({ bool accessibleNavigation }) { return tester.pumpWidget(MaterialApp( home: MediaQuery( data: MediaQueryData(accessibleNavigation: accessibleNavigation), child: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: const Text('test'), action: SnackBarAction(label: 'foo', onPressed: () { }), )); }, 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); }); 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); }); testWidgets('Snackbar calls onVisible once', (WidgetTester tester) async { const Key tapTarget = Key('tap-target'); int called = 0; await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: const Text('hello'), duration: const Duration(seconds: 1), onVisible: () { called += 1; }, )); }, behavior: HitTestBehavior.opaque, child: Container( height: 100.0, width: 100.0, key: tapTarget, ), ); }, ), ), )); await tester.tap(find.byKey(tapTarget)); await tester.pump(); // start animation await tester.pumpAndSettle(); expect(find.text('hello'), findsOneWidget); expect(called, 1); }); testWidgets('Snackbar does not call onVisible when it is queued', (WidgetTester tester) async { const Key tapTarget = Key('tap-target'); int called = 0; await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: const Text('hello'), duration: const Duration(seconds: 1), onVisible: () { called += 1; }, )); Scaffold.of(context).showSnackBar(SnackBar( content: const Text('hello 2'), duration: const Duration(seconds: 1), onVisible: () { called += 1; }, )); }, behavior: HitTestBehavior.opaque, child: Container( height: 100.0, width: 100.0, key: tapTarget, ), ); }, ), ), )); await tester.tap(find.byKey(tapTarget)); await tester.pump(); // start animation await tester.pumpAndSettle(); expect(find.text('hello'), findsOneWidget); expect(called, 1); }); }