// 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_test/flutter_test.dart'; import 'package:flutter/material.dart'; void main() { testWidgets('SnackBar control test', (WidgetTester tester) async { const String helloSnackBar = 'Hello SnackBar'; const Key tapTarget = const Key('tap-target'); await tester.pumpWidget(new MaterialApp( home: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(const SnackBar( content: const Text(helloSnackBar), duration: const Duration(seconds: 2) )); }, behavior: HitTestBehavior.opaque, child: new 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 = const Key('tap-target'); await tester.pumpWidget(new MaterialApp( home: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { snackBarCount += 1; Scaffold.of(context).showSnackBar(new SnackBar( content: new Text('bar$snackBarCount'), duration: const Duration(seconds: 2) )); }, behavior: HitTestBehavior.opaque, child: new 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 = const Key('tap-target'); int time; ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController; await tester.pumpWidget(new MaterialApp( home: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { snackBarCount += 1; lastController = Scaffold.of(context).showSnackBar(new SnackBar( content: new Text('bar$snackBarCount'), duration: new Duration(seconds: time) )); }, behavior: HitTestBehavior.opaque, child: new 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 = const Key('tap-target'); await tester.pumpWidget(new MaterialApp( home: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { snackBarCount += 1; Scaffold.of(context).showSnackBar(new SnackBar( content: new Text('bar$snackBarCount'), duration: const Duration(seconds: 2) )); }, behavior: HitTestBehavior.opaque, child: new 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(new MaterialApp( home: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(new SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), action: new 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('SnackBar button text alignment', (WidgetTester tester) async { await tester.pumpWidget(new MaterialApp( home: new MediaQuery( data: const MediaQueryData( padding: const EdgeInsets.only( left: 10.0, top: 20.0, right: 30.0, bottom: 40.0, ), ), child: new Scaffold( body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(new SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), action: new 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 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 expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.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, 14.0 + 40.0); // margin + bottom padding }); testWidgets('SnackBar is positioned above BottomNavigationBar', (WidgetTester tester) async { await tester.pumpWidget(new MaterialApp( home: new MediaQuery( data: const MediaQueryData( padding: const EdgeInsets.only( left: 10.0, top: 20.0, right: 30.0, bottom: 40.0, ), ), child: new Scaffold( bottomNavigationBar: new BottomNavigationBar( items: const <BottomNavigationBarItem>[ const BottomNavigationBarItem(icon: const Icon(Icons.favorite), title: const Text('Animutation')), const BottomNavigationBarItem(icon: const Icon(Icons.block), title: const Text('Zombo.com')), ], ), body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(new SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), action: new 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 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 expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.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, 14.0); // margin (with no bottom padding) }); testWidgets('SnackBarClosedReason', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(); bool actionPressed = false; SnackBarClosedReason closedReason; await tester.pumpWidget(new MaterialApp( home: new Scaffold( key: scaffoldKey, body: new Builder( builder: (BuildContext context) { return new GestureDetector( onTap: () { Scaffold.of(context).showSnackBar(new SnackBar( content: const Text('snack'), duration: const Duration(seconds: 2), action: new SnackBarAction( label: 'ACTION', onPressed: () { actionPressed = true; } ), )).closed.then<Null>((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)); }); }